JS实现单向链表结构

337 阅读2分钟

JS实现单向链表

一、前言

单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始。链表是使用指针进行构造的列表,又称为节点列表,因为链表是由一个个节点组装起来的,其中每个节点都有指针成员变量指向列表中的下一个节点,head指针指向第一个成为表头节点,而终止于最后一个指向null的指针,如下图所示:

image.png

一个单向链表至少具备以下基础功能:

  • append 往链表尾部添加节点

  • insert 指定位置插入节点

  • remove 根据内容移除某个节点

  • removeAt 根据索引移除某个节点

  • indexOf 返回某个节点的索引

  • isEmpty 链表是否为空

  • size 返回链表长度

    下面开始使用js实现一个简单的单项链表

二、单向链表的实现

const LinkedList = (function() {
  // 节点类
  class Node {
    constructor(element) {
      // 保存当前节点的值
      this.element = element
      // 指向下一个节点的指针,初始为null
      this.next = null
    }
  }
​
  // 链表长度
  let length = new WeakMap()
  // 头节点
  let head = new WeakMap()
​
  return class {
    constructor() {
      // 初始长度为0
      length.set(this, 0)
      // 初始头节点为null
      head.set(this, null)
    }
    // 往链表尾部添加节点
    append(element) {
      // 创建新节点
      const node = new Node(element)
      let current = null
​
      // 头节点为null,说明链表此时为第一次添加节点
      if (this.getHead() === null) {
        head.set(this, node)
      } else {
        // 缓存头节点
        current = this.getHead()
        // 通过while循环找到尾节点,然后在尾节点添加新节点
        while(current.next) {
          current = current.next
        }
        // 将新节点添加到尾节点后面
        current.next = node
      }
      // 链表长度加1
      this._sizeAdd()
    }
    // 指定位置插入节点
    insert(element, position) {
      // position需在0-length之间
      if (position >= 0 && position <= this.size()) {
        // 创建新节点
        const node = new Node(element)
        let current = this.getHead(), // 缓存旧头节点 
            index = 0, // 查询下标
            previous = null // 插入位置的前一个节点
​
        // position等于0代表要插入到最前面
        if (position === 0) {
          // 新节点的指针指向旧头节点
          node.next = current
          // 将插入节点作为新的头节点
          head.set(this, node)
        } else {
          while(index++ < position) {
            previous = current
            current = current.next
          }
          // 把节点插入到指定位置
          node.next = current
          previous.next = node
        }
        // 链表长度加一
        this._sizeAdd()
​
        return true
      } else {
        return false
      }
    }
    // 根据索引移除节点
    removeAt(position) {
      if (position >= 0 && position < this.size()) {
        let current = this.getHead(),
            previous = null,
            index = 0
        // position为0代表删除头节点
        if (position === 0) {
          // 设置新头节点为旧头节点的下一个节点
          head.set(this, current.next)
        } else {
          while(index++ < position) {
            previous = current
            current = current.next
          }
          // 将索引对应节点的上一个节点指针指向索引对应节点的下一个节点,即可完成节点的删除
          previous.next = current.next
        }
        // 链表长度减1
        this._sizeSub()
​
        // 返回被删除节点
        return current.element
      } else {
        return false
      }
    }
    // 根据节点对应的值来删除节点
    remove(element) {
      // 找到对应下标
      const index = this.indexOf(element)
      return this.removeAt(index)
    }
    // 返回节点下标
    indexOf(element) {
      let current = this.getHead(),
          index = 0
      while(current) {
        // 找到当前元素后,返回对应下标
        if (element === current.element) {
          return index
        }
        index++
        current = current.next
      }
      return -1
    }
    // 链表是否为空
    isEmpty() {
      return this.getHead() === null
    }
    // 获取头节点
    getHead() {
      return head.get(this)
    }
    // 获取链表长度
    size() {
      return length.get(this)
    }
    // 链表长度加1
    _sizeAdd() {
      let len = this.size()
      len++
      length.set(this, len)
    }
    _sizeSub() {
      let len = this.size()
      len--
      length.set(this, len)
    }
  }
})()
const link = new LinkedList()
console.log(link.isEmpty()) // true
link.append(0)
link.append(1)
link.append(2)
link.append(3)
console.log(link.indexOf(3)) // 3
link.insert(1.5, 2)
console.log(link.size()) // 5
console.log(link.removeAt(2)) // 1.5
console.log(link.indexOf(1.5)) // -1
console.log(link.remove(3)) // 3
console.log(link.size()) // 3
console.log(link.isEmpty()) // false