1,什么是双向链表?
-
双向链表的节点会多一个指向前一个节点的指向prev
-
双向链表的头节点的prev指向null,尾节点的next指向null
-
链表的设计LinkedList会有一个指向头节点的first,指向为节点的last
1.1,双向链表的初始化
-
相比较于单项链表,多了一个first指向和节点的指向prev
public class LinkedList3 extends AbstractList {
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; } }}
2,查询方法的实现
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--) {//元素从index到size-1进行查询
node = node.prev;
}
return node;
}
}
- 双向链表的查询和之前的单项链表有很大的不同,查询的效率更高
- 在查询时,可以判断索引是在size的前1/2,还是size的后1/2。
- 在size的前1/2: 元素在index为0到传入索引开始查询,node.next赋值给node
- 在size的后1/2:元素从index到size-1进行查询,倒叙查询,node.prev赋值给node
3,插入元素
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;
}
-
插入元素就是在,当前的index的位置插入新的元素
-
保存index位置的节点node为next
-
保存index位置之前index-1位的节点node为prev
-
在index位置插入新的节点newNode,newNode的属性prev应该指向index-1位置的节点prev,newNode的属性next应该指向原来index位置的节点next
-
原来index位置的节点next的prev指向newNode
-
index-1节点位置的next指向newNode
当新插入的节点索引为
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;
}
}
- 当传入节点索引为0时,也就是当前节点的prev属性为null
- 此时只需要first指向当前的属性newNode
当插入的节点位置是链表的最后时, 因为
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++;
}
- 当传入的index==size的时候,表示在最后一个位置插入元素
- 先保存最后位置的节点为oldLast
- last属性指向插入的新的节点newNode,newNode的prev属性指向oldLast节点,newNode的next属性指向null
- oldLast的next属性指向插入的新节点,也就是last
4,删除节点
public E remove(int index) {
// 需要删除的节点
Node<E> node = node(index);
// 删除节点的前一个节点
Node<E> prev = node.prev;
// 删除节点的后一个节点
Node<E> next = node.next;
// 删除节点, 只需要去掉对这个节点的引用即可
// 如果prev == null, 说明删除的是第一个节点,index== 0
if (prev == null) {
first = next;
}else {
prev.next = next;
}
// 如果next == null, 说明删除的是最后一个节点,index==size-1
if (next == null) {
last = prev;
}else {
next.prev = prev;
}
size--;
return node.element;
}
- 删除节点:就是让被删除的当前的节点失去前后节点的指向
- 同样需要特殊处理,删除第一个位置节点和最后位置的节点
- 最后要让size--
5,清空链表
public void clear() {
size = 0;
first = null;
last = null;
}
- 需要size为0,first和last的指向全部为
6,双向链表和动态数组的比较
- 动态数组:开辟,销毁内存空间的次数相对较少,但是可能造成内存空间的浪费(可以通过缩容来解决)
- 双向链表:开辟,销毁内存空间的次数相对较多,但是不会造成内存空间的浪费
- 如果频繁的在尾部进行添加,删除操作,两者均可选择
- 如果频繁的在头部进行添加,删除操作,建议选择双向链表
- 如果有频繁的查询操作,建议使用动态数组