相对于单向链表,双向链表的有点是:对于给定的节点,可以从两个方向进行操作,而单向链表只能向后继方向操作,除非持有了该节点的前驱节点。因为双向链表中的节点需要持有其前驱节点以及后驱节点,因此操作上相对于单向链表略微复杂,在存储上也需要更多的存储空间。

下面是一个双向链表的简单示意图。

image.png

针对其节点的代码描述为

  1. @Data
  2. static class DLLNode<T> {
  3. // 节点数据
  4. private T data;
  5. // 节点的前驱节点,如果该节点为头节点,previous则为NULL
  6. private Node<T> previous;
  7. // 节点的后驱节点,如果该节点为尾结点,next则为NULL
  8. private Node<T> next;
  9. }

双向链表的遍历操作

双向链表的遍历和单向链表类似,从前向后遍历是相同的代码逻辑,双向链表比单向链表多了一个从后向前的遍历方式,和从前向后遍历大同小异。

  1. public <T> void foreachDLLNode(Node<T> node) {
  2. // 从前向后遍历
  3. while (node != null) {
  4. System.out.println(node.data);
  5. node = node.next;
  6. }
  7. // 从后向前遍历
  8. while (node != null) {
  9. System.out.println(node.data);
  10. node = node.previous;
  11. }
  12. }

双向链表的插入操作

同单向链表类似,双向链表的插入也分为从链表的头部插入,中间插入以及从尾部插入,下面笔者从这三种方式讲解。

从链表头部插入数据

从链表的头部插入数据分为几部,其示意图如下

image.png

  1. 将新的节点 A0 的next 指向原表头
  2. 将原表头的 previous 指向新节点A0

其代码逻辑如下所示

  1. public static <T> Node<T> insertNode(Node<T> newNode, Node<T> source) {
  2. newNode.next = source;
  3. source.previous = newNode;
  4. return newNode;
  5. }

从链表中间插入数据

从双向链表中间插入数据分为4步,其示意图如下所示

image.png

  1. 将新节点的 previsou 执行插入点节点 source
  2. 将插入点的 source 的next指向新节点
  3. 向新节点的next指向 source 的next节点
  4. source的next节点的 previous 指向新节点
  1. public static <T> void insertNode(Node<T> newNode, Node<T> source) {
  2. // 获取插入节点的后驱节点
  3. Node<T> next = source.next;
  4. // 将新节点的前驱设置正确
  5. newNode.previous = source;
  6. source.next = newNode;
  7. // 将新节点的next节点设置正确
  8. newNode.next = next;
  9. next.previous = newNode;
  10. }


从链表尾部插入数据

从链表尾部插入数据和从头部插入数据类似,其步骤分为2 步

image.png

  1. 将尾结点的next执行新节点
  2. 向新节点的previous执行尾结点
  1. public static <T> void insertNode(Node<T> newNode, Node<T> end) {
  2. end.next = newNode;
  3. newNode.previous = end;
  4. // 如有必要
  5. newNode.next = null;
  6. }

双向链表的删除操作

同插入数据一样,删除操作也分为从表头删除,中间删除以及尾部删除

从链表头部删除

其示意图如下
image.png

  1. 找到新的头部节点,将新的头部节点 与原节点断开
  2. 将原节点的next断开
  3. 返回新的节点

    1. public static <T> Node<T> removeNodeFromHead(Node<T> target) {
    2. // 新的头部节点
    3. Node<T> newHeadNode = target.next;
    4. // 断开与原头节点的关联
    5. target.next = null;
    6. newHeadNode.previous = null;
    7. // help GC
    8. target = null;
    9. return newHeadNode;
    10. }

    从链表中间删除

其示意图如下
image.png

  1. 断开被删除节点D的前驱引用以及后驱引用
  2. 将前驱节点C的next指向 E
  3. 将后驱节点E的previous 指向 C
  1. public static <T> void removeNode(Node<T> target) {
  2. // 新的头部节点
  3. Node<T> next = target.next;
  4. Node<T> previous = target.previous;
  5. previous.next = null;
  6. target.next = null;
  7. next.previous = null;
  8. target.previous = null;
  9. // help GC
  10. target = null;
  11. previous.next = next;
  12. next.previous = previous;
  13. }

从链表尾部删除

从链表尾部删除,和从链表头部删除类似

image.png

  1. 删除尾部节点E的previous 引用
  2. 删除尾部节点E的前驱节点D的next引用
  1. public static <T> void removeNode(Node<T> end) {
  2. Node<T> previous = end.previous;
  3. end.previous = null;
  4. previous.next = null;
  5. // help GC
  6. end = null;
  7. }