一、双向链表
- 单向链表只能通过Node中
next
属性从头遍历链表,完成搜索。 - 双向链表中的Node增加
prev
属性,指向该节点上一个节点。 - 双向链表查找元素可以从
first
或last
两个方向开始查找。
二、双向链表接口设计
- 相较于单项链表,双向链表需要重写
查找节点
、插入节点
、删除节点
、清空节点
四个方法。
三、双向链表的实现
1、构造方法
- 在双向链表属性中增加
last
属性记录尾节点。在Node
属性中增加prev
属性记录上一个节点。
public class LinkedList<E> extends AbstractList<E> {
private Node<E> first;
//增加last节点
private Node<E> last;
private static class Node<E> {
E element;
//增加上一个节点
Node<E> prev;
Node<E> next;
public Node(Node<E> prev, E element, Node<E> next) {
this.prev = prev;
this.element = element;
this.next = next;
}
}
}
2、查找节点
- 通过判断查找的节点在链表的前半段或后半段,选择
first
节点从前往后
遍历或last
节点从后往前
遍历。
private Node<E> node(int index) {
rangeCheck(index);
// 判断节点是在链表前一半还是后一半
if (index < (size >> 1)) {
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
} else {
Node<E> node = last;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
3、清空节点
- 需将新增属性
last
置为null
。
public void clear() {
size = 0;
first = null;
last = null;
}
4、插入节点
- 插入位置原节点为
old_node
(下图2号节点),上一个节点为pre_node
(下图1号节点),新节点为new_node
。 pre_node
的next
现在为new_node
。old_node
的prev
现在为new_node
。new_node
的prev
即为old_node
的prev
。new_node
的next
为old_node
。- 特殊情况,插入在最前面:
- 如果
old_node
的prev
为null
,那么old_node
即为原头节点。那么双向链表的first
属性需改为new_node
。
- 如果
- 插入在最后面:
new_node
的prev
为双向链表的last
,next
为null
,同时last
属性需改为new_node
。- 如果原链表为空,则双向链表的
first
和last
都为new_node
。
public void add(int index, E element) {
rangeCheckForAdd(index);
// 往最后面添加元素
if (index == size) {
Node<E> oldLast = last;
last = new Node<>(oldLast, element, null);
// 这是链表添加的第一个元素
if (oldLast == null) {
first = last;
} else {
oldLast.next = last;
}
} else {
//插入位置的原节点,即为新节点的next节点。
Node<E> next = node(index);
//新添加节点的上一个节点,即为该位置原节点的上一个节点。
Node<E> prev = next.prev;
//创建新添加节点。
Node<E> node = new Node<>(prev, element, next);
//原节点的上一个节点,为新添加节点。
next.prev = node;
// index == 0
if (prev == null) {
first = node;
} else {
//原节点上一个节点的next,即为新添加节点。
prev.next = node;
}
}
size++;
}
5、删除节点
- 删除节点, 只需要让被删除节点的前一个节点与后一个节点之间链接, 同时去掉被删除节点引用即可。
- 需要注意的是, 第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;
}
6、清空节点
- 清空节点, 需要将first和last的引用全部断开。
public void clear() {
size = 0;
first = null;
last = null;
}
到此为止,我们即完成了从单向链表到双向链表的改造。