JS数据结构之链表(LinkedList)

232 阅读1分钟

节点结构

保存 element 节点元素和 next 指针即可:

function Node(element, next) {
  this.element = element // 节点元素
  this.next = next // 下一个节点指针
}

链表结构

这里保存了头尾指针和节点个数,其中保存尾指针的目的是快速在尾部添加元素,否则要从头开始遍历。

function LinkedList() {
  this.head = null // 头指针
  this.tail = null // 尾指针
  this.size = 0 // 节点个数
}

末尾添加元素

如果末尾元素不存在,表明是空链表,否则就添加进去,并把 head 和 tail 都设置成该元素。

LinkedList.prototype.push = function (element) {
  const node = new Node(element, null)
  if (this.tail === null) {
    this.head = this.tail = node
  } else {
    this.tail.next = node
    this.tail = node
  }
  return ++this.size
}

弹出末尾元素

如果链表长度为1,则需要置空链表,把 head 和 tail 都设置成 null,否则就遍历找到倒数第二个节点,设置其为 tail 并把它的 next 置空即可。

LinkedList.prototype.pop = function (element) {
  if (this.tail === null) return null
  let last
  if (this.size === 1) {
    last = this.tail
    this.head = null
    this.tail = null
  } else {
    let i = 0, prev = this.head
    while (i++ < this.size - 1) prev = prev.next
    last = prev.next
    prev.next = null
    this.tail = prev
  }
  this.size--
  return last
}

插入元素

先判断边界,只有在 [0, this.size] 之间的位置允许插入,先找到当前位置前面的那个元素 prev,将新元素的 next 设置为 prev.next,然后再将 prev.next 设置为该元素即可。同样要注意如果在头部插入需要额外处理 head。

LinkedList.prototype.insert = function (index, element) {
  if (index < 0 || index > this.size) throw new Error('索引越界')
  if (index === 0) {
    this.head = new Node(element, this.head)
    if (this.size === 0) this.tail = this.head
  } else {
    let i = 0, prev = this.head
    while (i++ < index - 1) prev = prev.next
    prev.next = new Node(element, prev.next)
    if (this.size === index) this.tail = prev.next
  }
  this.size++
}

删除元素

逻辑跟插入类似,都要找到指定位置之前的那个元素。这里要注意,删除头部和尾部元素时都需要特殊处理。

LinkedList.prototype.remove = function (index) {
  if (index < 0 || index >= this.size) throw new Error('索引越界')
  let current
  if (index === 0) {
    current = this.head
    this.head = current.next
    if (this.size === 1) this.tail = null
  } else {
    let i = 0, prev = this.head
    while (i++ < index - 1) prev = prev.next
    current = prev.next
    prev.next = current.next
    if (this.size === index - 1) this.tail = prev
  }
  current.next = null
  this.size--
  return current
}

获取元素

这个最简单,直接遍历即可。

LinkedList.prototype.get = function (index) {
  if (index < 0 || index >= this.size) throw new Error('索引越界')
  let i = 0, current = this.head
  while (i++ < index) current = current.next
  return current
}

反转链表

把链表进行反转,即把 tail 变 head,head 变 tail,各 node 的 next 指向反过来。下面给出三种反转思路:

  1. 循环遍历,两两反转

    LinkedList.prototype.reverse = function () {
      if (this.size <= 1) return this
      let current = this.head, next = current.next 
      while (next) {
        const third = next.next
        next.next = current
        current = next
        next = third
      }
      this.head.next = null
      this.tail = this.head
      this.head = current
      return this
    }
    
  2. 递归遍历反转

    LinkedList.prototype.reverse = function () {
      const rev = (head) => {
        const next = head.next
        if (head === null || next === null) return head 
        const newHead = rev(next) 
        next.next = head
        head.next = null 
        return newHead
      }
      const newHead = rev(this.head)
      this.tail = this.head
      this.head = newHead
      return this
    }
    
  3. 循环遍历,逐个反转

    LinkedList.prototype.reverse = function () {
      let head = this.head, newHead = null 
      while (head !== null) {
        let tmp = head.next
        head.next = newHead
        newHead = head
        head = tmp
      }
      this.tail = this.head
      this.head = newHead
      return this
    }