如何用JS实现单链表?

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

什么是链表?

链表是一种由一群结点组成顺序的数据结构。在最简单的情况下,每个结点由一个数据和一个指向在顺序中下一个结点的指针(既连接)而组成。

如图~

image.png

设计思路

这里我也是在visualgo上的demo中分析到的(visualgo,程序员学算法必备网站)

通过一个class承载所需的属性方法

属性

  • head 头部
  • tail 尾部
  • size 链表长度
class CreateLinkList {
  private length: number;
  private head!: LinkListNode;
  private tail!: LinkListNode;
  constructor(arr: number[]) {
    const length = arr.length;
    if (!length) {
      throw new Error("arr is empty");
    }
    this.length = length;
  }
  size() {
    return this.length;
  }
  getHead() {
    return this.head;
  }
  getTail() {
    return this.tail;
  }
 }
复制代码

方法

  1. 创建

visualgo 动画演示 1_.gif 代码

class CreateLinkList {
  constructor(arr: number[]) {
    // 。。。
    this.create(arr);
  }
  private create(arr: number[]) {
    const length = this.length;
    let curNode: LinkListNode = {
      value: arr[length - 1],
    };
    this.tail = curNode;
    if (length === 1) {
      return curNode;
    }
    for (let i = length - 2; i >= 0; i--) {
      curNode = {
        value: arr[i],
        next: curNode,
      };
    }
    this.head = curNode;
  }
  // ...
}
复制代码
  1. 查找

visualgo 动画演示

1_.gif

visualgo这里是用value去做的查询,本人代码设计是用index坐标去查询

代码

// ...
class CreateLinkList {
  // ...
  get(index: number) {
    if (index < 0 || index >= this.length) {
      throw new Error('invalid index');
    }
    // 保留头尾的好处,查找时间复杂度为O(1);
    if (index === 0) {
      return this.head;
    }
    if (index === this.length - 1) {
      return this.tail;
    }
    // 除头尾获取其他项时间复杂度为O(n)
    let i = 0;
    let temp = this.head;
    while(i < index) {
      if (temp.next) {
        temp = temp.next;
        i++;
      }
    }
    return temp;
  }
 // ...
}
复制代码
  1. 插入

visualgo 动画演示 1_.gif

由动画看的出来,插入需要支持三点: 1.头部前插入;2.尾部后插入;3.中间项后插入

  • 在头部前插入
class CreateLinkList {
  // ...
   /**
   * @description 在头部前插入节点
   * @param value
   */
  beforHeadInsert(value: number): void {
    this.head = {
      value,
      next: this.head,
    };
    this.length++;
  }
  // ...
}
复制代码
  • 在尾部追加
class CreateLinkList {
  // ...
  /**
   * @description 在尾部插入节点
   * @param value
   */
  lastAppend(value: number) {
    this.tail.next = {
      value,
    };
    this.length++;
  }
  // ...
}
复制代码
  • 中间项后插入
class CreateLinkList {
  // ...
   /**
   * @description 在中间项后插入
   * @param index 索引
   * @param value 插入节点value
   */
  insert(index: number, value: number) {
    if (index < 0) {
      throw new Error('invalid index');
    }
    // 如果index >= 最大坐标,同在尾部插入
    if (index >= this.length - 1) {
      this.lastAppend(value);
    } else {
      let i = 0;
      let temp = this.head as LinkListNode;
      while(i <= index) {
        if (i < index) {
          temp = temp.next as LinkListNode;
        } else {
          const t = temp.next;
          const newNode = {
            value,
            next: t
          };
          temp.next = newNode;
        }
        i++;
      }
    }
  }
  // ...
}
复制代码
  1. 移除

visualgo 动画演示 1_.gif

移除也需要支持三点: 1.移除头部节点;2.移除尾部节点;3.移除中间节点

  • 移除头部节点
class CreateLinkList {
  // ...
  /**
   * @description 删除头部节点
   */
  removeHead() {
    if (this.length > 1) {
      this.head = this.head?.next as Required<LinkListNode>;
      this.length--;
    } else {
      this.head = null;
      this.tail = null;
      this.length--;
    }
  }
  // ...
}
复制代码
  • 移除节点
class CreateLinkList {
  // ...
  /**
   * @description 删除节点
   */
  remove(index: number) {
    if (index < 0 || index > this.length - 1 ) {
      throw new Error('invalid index');
    }
    if (index === 0) {
      this.removeHead();
    } else {
      let temp = this.head as NonNullable<LinkListNode>;
      let i = 0;
      while(i <= index - 1) {
        if (i < index - 1) {
          temp = temp?.next as LinkListNode;
        } else {
          if (temp && temp.next && temp.next.next) {
            temp.next = temp.next.next;
          } else {
            delete temp.next;
          }
        }
        i++;
      }
      this.length--;
    }
  }
  // ...
}
复制代码
  • 移除尾部节点
class CreateLinkList {
  // ...
  /**
   * @description 删除尾部节点
   */
  removeTail() {
    this.remove(this.length - 1);
  }
  // ...
}
复制代码

测试

通过使用jest进行了一些基本的单元测试,都是通过的..

image.png

到此为止,一个基础版的单链表生成了,单测可能不够充分,可能存在一些其他的问题,有感兴趣的小伙伴可以指点一下~ 源代码

f616ca9fc981d5e6214c5ef1a34fedf9.jpeg

猜你喜欢

转载自juejin.im/post/7085655093793685512