要存储多个元素,数组可能是最常用的数据结构,但是数组有个缺点是他的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素.而相对于传统的数组,链表的一个好处在于,添加或移除原属的时候不需要移动其他元素.因为链表使用的是指针,所以当我们要访问链表中间的一个元素的时候,我们需要从起点(表头)开始迭代链表,而不能像数组一样直接访问任何位置的任何元素.
因为链表不是ts的基础数据类型,所以想通过ts去实现一个链表的类.

创建链表

以下是LinkedList类的骨架:

  1. class LinkedList<T> {
  2. /**
  3. * 用来存储链表中的元素数量
  4. */
  5. protected count = 0;
  6. /**
  7. * 保存引用,由于链表是动态的,需要将第一个元素的引用保存下来
  8. * Node助手类,表示我们想要添加到链表的项
  9. */
  10. protected head: Node<T> | undefined;
  11. constructor() {}
  12. /**
  13. * 比较链表中的元素是否相等
  14. * @param a
  15. * @param b
  16. * @returns boolean
  17. */
  18. equalsFn<T>(a: T, b: T): boolean {
  19. return a === b;
  20. }
  21. }

助手类Node:通过这个类,我们可以很好的处理我们想要实现的链节点的类型和结构

  1. /**
  2. * 想要添加到链表的项
  3. */
  4. export class Node<T> {
  5. /**
  6. * @param element 要加入链表元素的值
  7. * @param next 指向链表中下一个元素的指针
  8. */
  9. constructor(public element: T, public next?: Node<T>) {}
  10. }

接下来要实现的是LinkedList类的方法:

  • push(element):向链表尾部添加一个元素;
  • insert(element,position):向链表的特定位置插入一个新元素;
  • getElementAt(index):返回链表中特定位置的元素.如果链表中不存在这样的元素,则返回undefined;
  • remove(element):从链表中移除一个元素;
  • indexOf(element):返回元素在链表中的索引,如果链表中没有该元素则返回-1;
  • removeAt(position):从链表的特定位置移除一个元素;
  • isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false;
  • size():返回链表包含的元素个数,与数组的length属性类似;
  • toString():返回表示整个链表的字符串.由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法.让其只输出元素的值;

    向链表尾部添加元素

    1. push(element: T) {
    2. // 创建Node项
    3. const node = new Node(element);
    4. // 指向链表current项的变量
    5. let current;
    6. // head元素为undefined或null,意味向链表添加第一个元素
    7. if (this.head == null) {
    8. // head指向node元素,node.next为null
    9. this.head = node;
    10. } else {
    11. // 链表不为空
    12. current = this.head;
    13. // 循环访问链表,直到找到最后一项,最后一项的next为null
    14. while (current.next != null) {
    15. current = current.next;
    16. }
    17. // 最后一项next指向node
    18. current.next = node;
    19. }
    20. // 递增链表的长度
    21. this.count++;
    22. }

    循环迭代链表直到目标位置

    1. /**
    2. * 循环迭代链表直到目标位置
    3. * @param index 目标索引
    4. * @returns undefeated||Node
    5. */
    6. getElementAt(index: number) {
    7. // 检查越界值
    8. if (index >= 0 && index <= this.count) {
    9. // 初始化current,从head开始
    10. let current = this.head;
    11. // 循环至index
    12. for (let i = 0; i < index && current != null; i++) {
    13. current = current.next;
    14. }
    15. return current;
    16. }
    17. return undefined;
    18. }

    从链表中移除元素

    移除元素,参数为元素在链表的位置

    1. /**
    2. * 从链表移除元素
    3. * @param index 要移除的元素位置
    4. * @returns undefined||element
    5. */
    6. removeAt(index: number) {
    7. // 检查越界值
    8. if (index >= 0 && index < this.count) {
    9. // 对链表中第一个元素的引用
    10. let current = this.head;
    11. // 移除第一项
    12. if (index === 0) {
    13. this.head = current.next;
    14. } else {
    15. // 获取指定位置的元素
    16. const previous = this.getElementAt(index - 1);
    17. // previous.next为要移除的元素
    18. current = previous.next;
    19. // 移除元素的next赋予previous.next,current会被丢弃在内存中,等着被垃圾回收器处理
    20. previous.next = current.next;
    21. }
    22. // 减小链表长度
    23. this.count--;
    24. return current.element;
    25. }
    26. return undefined;
    27. }

    链表包含元素个数

    该方法可以当做工具方法,因为count是protected,对外部是不可获取的,所以需要通过方法return出去,同时后面几个方法也是都有使用到

    1. size() {
    2. return this.count;
    3. }

    判断链表是否为空

    跟size同样是工具方法,直接使用size去判断长度是否为0

    isEmpty() {
      return this.size() === 0;
    }
    

    获取元素在链表索引

    /**
     *
     * @param element 获取index的元素
     * @returns -1~
     */
    indexOf(element: T) {
      let current = this.head;
      // 循环迭代比较是否是要查找的元素,返回index
      for (let i = 0; i < this.size() && current != null; i++) {
        if (this.equalsFn(element, current.element)) {
          return i;
        }
        current = current.next;
      }
      // 迭代完找不到返回-1
      return -1;
    }
    

    从链表中移除元素

    /**
     * 从链表中移除元素
     * @param element 所要移除的元素
     * @returns undefined||element
     */
    remove(element: T) {
      // 使用indexOf查找到该元素在链表的索引
      const index = this.indexOf(element);
      // 使用索引来移除元素
      return this.removeAt(index);
    }
    

    向链表插入元素

    /**
     * 向链表的特定位置插入新元素
     * @param element 所要插入的元素
     * @param index 元素插入位置
     * @returns boolean
     */
    insert(element: T, index: number) {
      // 排除临界值
      if (index >= 0 && index <= this.count) {
        // 新建Node类型
        const node = new Node(element);
        //在表头插入
        if (index === 0) {
          // 常规链表插入方式 head=>current,current=>node.next,node=>head
          const current = this.head;
          node.next = current;
          this.head = node;
        } else {
          // 非表头插入,获取插入点前一个node
          const previous = this.getElementAt(index - 1);
          node.next = previous.next;
          previous.next = node;
        }
        this.count++;
        return true;
      }
      // index超出临界值
      return false;
    }
    

    返回整个链表的字符串

    // 返回整个链表的字符串
    toString() {
      // 空链表直接返回空字符串
      if (this.head == null) {
        return "";
      }
      let objString = `${this.head.element}`;
      let current = this.head.next;
      // 循环迭代进行元素字符串化
      for (let i = 1; i < this.size() && current != null; i++) {
        objString = `${objString},${current.element}`;
        current = current.next;
      }
      return objString;
    }
    

    js版本

    下面的为js版本,不用进行编译,方便运行

    class Node {
    constructor(element, next) {
      this.element = element;
      this.next = next;
    }
    }
    class LinkedList {
    constructor() {
      this.count = 0;
      this.head = undefined;
    }
    equalsFn(a, b) {
      return a === b;
    }
    
    push(element) {
      const node = new Node(element);
      let current;
      if (this.head == null) {
        // catches null && undefined
        this.head = node;
      } else {
        current = this.head;
        while (current.next != null) {
          current = current.next;
        }
        current.next = node;
      }
      this.count++;
    }
    getElementAt(index) {
      if (index >= 0 && index <= this.count) {
        let node = this.head;
        for (let i = 0; i < index && node != null; i++) {
          node = node.next;
        }
        return node;
      }
      return undefined;
    }
    insert(element, index) {
      if (index >= 0 && index <= this.count) {
        const node = new Node(element);
        if (index === 0) {
          const current = this.head;
          node.next = current;
          this.head = node;
        } else {
          const previous = this.getElementAt(index - 1);
          node.next = previous.next;
          previous.next = node;
        }
        this.count++;
        return true;
      }
      return false;
    }
    removeAt(index) {
      if (index >= 0 && index < this.count) {
        let current = this.head;
        if (index === 0) {
          this.head = current.next;
        } else {
          const previous = this.getElementAt(index - 1);
          current = previous.next;
          previous.next = current.next;
        }
        this.count--;
        return current.element;
      }
      return undefined;
    }
    remove(element) {
      const index = this.indexOf(element);
      return this.removeAt(index);
    }
    indexOf(element) {
      let current = this.head;
      for (let i = 0; i < this.size() && current != null; i++) {
        if (this.equalsFn(element, current.element)) {
          return i;
        }
        current = current.next;
      }
      return -1;
    }
    isEmpty() {
      return this.size() === 0;
    }
    size() {
      return this.count;
    }
    getHead() {
      return this.head;
    }
    clear() {
      this.head = undefined;
      this.count = 0;
    }
    toString() {
      if (this.head == null) {
        return "";
      }
      let objString = `${this.head.element}`;
      let current = this.head.next;
      for (let i = 1; i < this.size() && current != null; i++) {
        objString = `${objString},${current.element}`;
        current = current.next;
      }
      return objString;
    }
    }