返回

带着双向链表,踏上数据结构的环形之旅!

前端

在数据结构的绮丽花园中,双向链表宛如一朵灵动曼妙的花朵,以其独特的身姿和优雅的舞姿,吸引着无数程序员的目光。它不同于单链表的单向行驶,而是以双向指针为纽带,将数据串联起来,形成一个环形之旅的绝妙结构。

双向链表,顾名思义,就是链表的每一位成员,也就是节点,都有着双向的联结,就像在一个环形跑道上,每一个选手都与左右的选手携手,形成了一个紧密而灵活的合作团体。这样的结构,给程序员们带来了许多便利。

首先,双向链表有着更加便捷的遍历方式。相较于单链表只能从头到尾依次读取数据,双向链表可以通过前向指针和后向指针,实现从任意一点开始,正向或反向遍历整个链表。这种灵活性,极大地提高了程序的执行效率,尤其是当我们需要在数据中进行快速查询或更新时,双向链表无疑是更胜一筹的选择。

其次,双向链表在删除操作上也更加高效。在单链表中,删除一个节点需要从头开始遍历,找到目标节点的前驱节点,再进行删除操作。而双向链表则不然,我们可以直接通过后向指针找到目标节点的前驱节点,直接进行删除操作,省去了遍历的步骤,效率大大提高。

双向链表的优点还不止于此。它还可以在插入操作上大显身手。在单链表中,插入一个新节点需要从头开始遍历,找到插入点的前驱节点,再进行插入操作。而双向链表则可以通过后向指针直接找到插入点的前驱节点,直接进行插入操作,同样省去了遍历的步骤,大大提高了效率。

当然,双向链表也不是十全十美的。相较于单链表,双向链表的每个节点都需要存储两个指针,这会带来更多的空间开销。不过,对于大多数应用场景来说,这种空间开销是值得的,毕竟双向链表带来的效率提升是显而易见的。

总而言之,双向链表是一种非常灵活、高效的数据结构,它在各种应用场景中都有着广泛的应用。无论是实现队列、栈还是其他复杂的数据结构,双向链表都是一个非常不错的选择。

下面,我们就来手写一个双向链表,并实现一些基本的方法,让大家对双向链表的原理和应用有更深入的了解。

class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
    this.prev = null;
  }
}

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

  // 添加元素到链表尾部
  append(data) {
    const newNode = new Node(data);
    if (this.length === 0) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      this.tail.next = newNode;
      newNode.prev = this.tail;
      this.tail = newNode;
    }
    this.length++;
  }

  // 从链表中删除指定元素
  remove(data) {
    let current = this.head;
    while (current) {
      if (current.data === data) {
        if (current === this.head) {
          this.head = current.next;
          if (this.head) {
            this.head.prev = null;
          }
        } else if (current === this.tail) {
          this.tail = current.prev;
          if (this.tail) {
            this.tail.next = null;
          }
        } else {
          current.prev.next = current.next;
          current.next.prev = current.prev;
        }
        this.length--;
        break;
      }
      current = current.next;
    }
  }

  // 在链表指定位置插入元素
  insertAt(index, data) {
    if (index < 0 || index > this.length) {
      throw new Error('Index out of bounds');
    }
    const newNode = new Node(data);
    if (index === 0) {
      this.head = newNode;
      newNode.next = this.head;
      if (this.tail === null) {
        this.tail = newNode;
      }
    } else if (index === this.length) {
      this.append(data);
    } else {
      let current = this.head;
      for (let i = 0; i < index - 1; i++) {
        current = current.next;
      }
      newNode.next = current.next;
      current.next = newNode;
      newNode.prev = current;
      newNode.next.prev = newNode;
    }
    this.length++;
  }

  // 获取链表指定位置的元素
  getAt(index) {
    if (index < 0 || index >= this.length) {
      throw new Error('Index out of bounds');
    }
    let current = this.head;
    for (let i = 0; i < index; i++) {
      current = current.next;
    }
    return current.data;
  }

  // 获取链表长度
  size() {
    return this.length;
  }

  // 判断链表是否为空
  isEmpty() {
    return this.length === 0;
  }

  // 打印链表元素
  print() {
    let current = this.head;
    while (current) {
      console.log(current.data);
      current = current.next;
    }
  }
}

这个手写的双向链表,涵盖了插入、删除、获取、更新等基本操作,以及链表长度、是否为空等辅助方法。这些方法的实现都充分利用了双向链表的特性,让操作变得更加高效。

双向链表,作为数据结构家族中的一员,以其独特的双向联结方式,在各种应用场景中大放异彩。它不仅可以作为队列、栈等基本数据结构的基础,还可以用于实现更复杂的算法和数据结构,如哈希表、图等。可以说,双向链表是程序员们必备的工具之一。

所以,如果你想在数据结构领域更上一层楼,双向链表绝对是你不可或缺的伙伴。从今天开始,就让我们携手双向链表,在数据结构的海洋中尽情遨游吧!