JS数据结构之双向链表


这篇文章是JavaScript实现链表数据结构的第三篇文章,本文的主要内容是——双向链表。之前我们已经实现过单向链表单向循环链表数据结构。

双向链表结构

由名字就可以看出来,和单向链表的结构相比,双向链表的特点在于双向,链表中节点除了next指针之外还有一个prev指针,next单向链表一样指向当前节点的下一个节点,prev则指向当前节点的前一个节点。双向链表的结构图如下所示:

双向链表数据结构

下面我们就来定义一下相关的数据结构,首先来定义节点的数据结构,除了保存数据之外,每个节点还需要一个指向前一个节点的 prev 指针和指向下一个节点的 next 指针,代码如下:

class Node {
  constructor(element, prev, next) {
    this.element = element;
    this.prev = prev;
    this.next = next;
  }
}

class DoublyLinkedList {
  constructor() {
    this.head = null;
    this.size = 0;
  }
}

增(add)、删(remove)、改(update)、查(getNode)

在实现了链表结构之后,接着就是增(add)、删(remove)、改(update)、查(getNode)这些基本方法了。

查(getNode)

这个方法和单向链表的方法实现一样,主要思想是遍历,这里就不再赘述。代码如下:

getNode(index) {
  if (index >= this.size) return new Error("index 超出列表长度");
  let offset = 0;
  let curNode = this.head;
  while (offset++ < index && curNode.element) {
    curNode = curNode.next;
  }
  return curNode;
}

改(update)

这个方法也是和单向链表一样,找到对应的节点更新数据即可。代码如下:

update(index, element) {
  const updateNode = this._getNode(index);
  updateNode.element = element;
  return updateNode;
}

增(add)

这个方法也和单向链表类似,只不过还需要更新prev指针的指向。这里可以分成两种情况讨论:

  1. 在链表的头节点处插入节点;这种情况下,新插入的节点就是新头节点,此时需要将旧头节点prev指针指向新头节点,然后将新头节点next指向旧头节点;如下图所示:
    插入头节点
  2. 在其他地方插入节点;这种情况下则找到要插入的索引处的前一个节点,将前一个节点的next指针指向待插入的节点,并将待插入节点next指针指向前一个节点原本next指向的节点,并将待插入节点prev指针指向前一个节点;如下图所示:
    插入其他节点

代码如下:

add(index, element) {
  if (arguments.length === 1) {
    element = index;
    index = this.size;
  }
  if (index > this.size) throw new Error("index 超出列表长度");
  if (index === 0) {
    // 在表头插入
    const oldHead = this.head;
    this.head = new Node(element, null, oldHead);
    if (oldHead) oldHead.prev = this.head;
  } else {
    const prevNode = this._getNode(index - 1);
    const newNode = new Node(element, prevNode, prevNode.next);
    // 这里需要兼容一下preNode已经是最后一个节点的情况
    if (prevNode.next) prevNode.next.prev = newNode;
    prevNode.next = newNode;
  }
  this.size++;
}

删(remove)

删除也可以分为两种情况讨论:

  1. 删除头节点;删除头节点时更新链表的头指针即可;如下图所示:
    删除头节点
  2. 其他情况;当删除链表中的节点时,则注意需要更新删除节点前后两个节点的指针;
    删除其他节点
remove(index) {
  if (index === 0) {
    // 如果是删除头节点
    this.head = this.head.next;
    this.head.prev = null;
  } else {
    const prevNode = this._getNode(index - 1);
    const removeNode = this._getNode(index);
    prevNode.next = removeNode.next;
    if (removeNode.next) removeNode.next.prev = prevNode;
  }
  this.size--;
}

完整代码

class Node {
  constructor(element, prev, next) {
    this.element = element;
    this.prev = prev;
    this.next = next;
  }
}

class DoublyLinkedList {
  constructor() {
    this.head = null;
    this.size = 0;
  }
  _getNode(index) {
    if (index >= this.size) return new Error("index 超出列表长度");
    let offset = 0;
    let curNode = this.head;
    while (offset++ < index && curNode.element) {
      curNode = curNode.next;
    }
    return curNode;
  }
  getNode(index) {
    return this._getNode(index);
  }
  update(index, element) {
    const updateNode = this._getNode(index);
    updateNode.element = element;
    return updateNode;
  }
  add(index, element) {
    if (arguments.length === 1) {
      element = index;
      index = this.size;
    }
    if (index > this.size) throw new Error("index 超出列表长度");
    if (index === 0) {
      // 在表头插入
      const oldHead = this.head;
      this.head = new Node(element, null, oldHead);
      if (oldHead) oldHead.prev = this.head;
    } else {
      const prevNode = this._getNode(index - 1);
      const newNode = new Node(element, prevNode, prevNode.next);
      // 这里需要兼容一下preNode已经是最后一个节点的情况
      if (prevNode.next) prevNode.next.prev = newNode;
      prevNode.next = newNode;
    }
    this.size++;
  }

  remove(index) {
    if (index === 0) {
      // 如果是删除头节点
      this.head = this.head.next;
      this.head.prev = null;
    } else {
      const prevNode = this._getNode(index - 1);
      const removeNode = this._getNode(index);
      prevNode.next = removeNode.next;
      if (removeNode.next) removeNode.next.prev = prevNode;
    }
    this.size--;
  }

  display() {
    let index = 0;
    let curNode = this.head;
    let res = "";
    while (++index <= this.size && curNode) {
      res += `${curNode.element}${index === this.size ? "" : " -> "}`;
      curNode = curNode.next;
    }
    console.log(res);
  }
}

参考


文章作者: CassielLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CassielLee !
评论
 上一篇
JS数据结构之双向循环链表 JS数据结构之双向循环链表
本篇文章是JavaScript链表数据结构的第四篇,本文主要是用JavaScript实现双向循环链表这一数据结构以及基础的增、删、改、查等方法。
2021-08-30
下一篇 
JS数据结构之单向循环链表 JS数据结构之单向循环链表
链表的扩展之单向循环链表,和单向链表相比,单向循环链表的主要特点就是循环,也就是其最后一个节点的next是指向其头节点的。因为这个特点单向循环链表在实际应用中存在很多的应用场景。这篇文章主要就是尝试用JavaScript实现单向循环链表的数据结构以及基本的增删改查的方法,最后还看了两个比较经典的单向循环链表相关的编程题。
2021-07-13
  目录