本篇是恋上数据结构与算法(第一季)的学习笔记, 使用JAVA语言
一、双向链表
- 与单向链表不同的是, 双向链表多了从后向前的指向
- 使用双向链表可以加快节点的查询速度
二、设计双向链表
- 与单项链表不同的是, 双向链表增加了
last
指针, 同时节点增加了向前的指向prev
属性
public class LinkedList3<E> extends AbstractList<E> {
private Node<E> first;
private Node<E> last;
private static class Node<E> {
E element;
Node<E> next;
Node<E> prev;
public Node(Node<E> prev, E element, Node<E> next) {
this.element = element;
this.next = next;
this.prev = prev;
}
}
}
另外, 与单项链表不同的方法实现是
查询节点
、插入节点
、删除节点
、清空节点
这四个方法
1、查询节点
- 因为是双向链表, 所以查询节点可以从后向前查询
- 查询节点的方向可以根据节点总数的一半, 即
size >> 1
为分隔, 索引小于一半的从前向后查询, 索引大于等于一半的从后向前查询
private Node<E> node(int index) {
rangeCheck(index);
// 根据节点数量的一半进行区分
if (index < (size >> 1)) {
Node<E> node = first;
// 小于size >> 1的节点从前向后查询
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}else {
Node<E> node = last;
// 大于等于 size >> 1 的节点从后向前查询
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
2、插入节点
- 插入节点, 就是在两个节点之间加入新的节点
- 首先要找到需要插入节点位置的原节点, 原节点要成为新节点的下一个节点
- 原节点的上一个节点成为新节点的上一个节点
public void addNode(int index, E element) {
// 新节点后的下一个节点, 就是原链表 index 位置的节点
Node<E> next = node(index);
// 新节点后的上一个节点, 就是原链表 index-1 位置的节点
Node<E> prev = next.prev;
// 创建新节点, 新节点的上一个节点时prev, 新节点的下一个节点是next
Node<E> node = new Node<>(prev, element, next);
// next的上一个节点是新节点
next.prev = node;
// prev的下一个节点是新节点
prev.next = node;
}
- 当新插入的节点索引为
0
时, 需要做特殊处理, 因为first
引用着0
节点
public void addNode(int index, E element) {
// 新节点后的下一个节点, 就是原链表 index 位置的节点
Node<E> next = node(index);
// 新节点后的上一个节点, 就是原链表 index-1 位置的节点
Node<E> prev = next.prev;
// 创建新节点, 新节点的上一个节点时prev, 新节点的下一个节点是next
Node<E> node = new Node<>(prev, element, next);
// next的上一个节点是新节点
next.prev = node;
// 当next.prev == null时, 说明新添加节点的索引是0
if (next.prev == null) {
// 此时first应该指向引得索引
first = node;
}else {
// prev的下一个节点是新节点
prev.next = node;
}
}
- 当插入的节点位置是链表的最后时, 因为
last
属性引用最后一个节点, 所以也需要做特殊处理 - 最终的插入方法如下
public void add(int index, E element) {
rangeCheckForAdd(index);
// 如果 index == size, 说明添加的索引是最后位置
if (index == size) {
Node<E> oldLast = last;
// 新创建节点, 新节点的next = null, prev = 之前的最后节点
last = new Node<>(oldLast, element, null);
// 旧链表的最后节点的下一个节点是 新创建的节点
oldLast.next = last;
}else {
// 添加新节点后的下一个节点
Node<E> next = node(index);
// 添加新节点后的上一个节点
Node<E> prev = next.prev;
// 创建新节点, 新节点的上一个节点时prev, 新节点的下一个节点是next
Node<E> node = new Node<>(prev, element, next);
// next的上一个节点是新节点
next.prev = node;
// 当next.prev == null时, 说明新添加节点的索引是0
if (next.prev == null) {
first = node;
}else {
// prev的下一个节点是新节点
prev.next = node;
}
}
size++;
}
3、删除节点
- 删除节点, 只需要让被删除节点的前一个节点与后一个节点之间相互联系, 同时去掉所有对被删除节点引用即可
- 需要注意的是, 查出
第0个节点
和最后一个节点
要特殊处理
public E remove(int index) {
// 需要删除的节点
Node<E> node = node(index);
// 删除节点的前一个节点
Node<E> prev = node.prev;
// 删除节点的后一个节点
Node<E> next = node.next;
// 删除节点, 只需要去掉对这个节点的引用即可
// 如果prev == null, 说明删除的是第一个节点
if (prev == null) {
first = next;
}else {
prev.next = next;
}
// 如果next == null, 说明删除的是最后一个节点
if (next == null) {
last = prev;
}else {
next.prev = prev;
}
size--;
return node.element;
}
4、清空节点
- 清空节点, 需要将
first
和last
的引用全部断开
public void clear() {
size = 0;
first = null;
last = null;
}