前言
双向链表中比较难懂的地方是节点间的引用关系。
本文主要分析双向链表添加和删除,使用图片来解释引用间的指向关系,希望能帮助各位理清思路。
基础要点
(1)双向链表既可从头找到尾,也可以从尾找到头。
(2)双向链表中每个节点自身存储有数据和俩个引用,一个指向前面的节点(prev),一个指向后面的节点(next)。
基本结构
(1)first和last分别作为链表的头部和尾部。
(2)通过对象引用找到对方。
(3)Node类主要用来将数据包起来成为一个节点,并存储前后节点的引用。
public class LinkedList<T> {
/** 记录链表头部的节点 */
private Node<T> first;
/** 记录链表尾部的节点 */
private Node<T> last;
/** 链表中节点的总数量 */
private int size;
private class Node<T> {
// 前一个节点的引用
private Node<T> prev;
// 节点存储的数据
private T val;
// 后一个节点的引用
private Node<T> next;
public Node() {
}
public Node(Node<T> prev, T val, Node<T> next) {
this.prev = prev;
this.val = val;
this.next = next;
}
}
}
添加节点
添加第一个节点(头插尾插都行),存储数据:1。
第一个加进来的新节点既是头也是尾,前后都没有节点。
接着上面,继续向头节点追加第二个节点(addFirst),存储数据:2
(1)新节点的next引用 指向 头节点。
(2)头节点的prev引用 指向 新节点,完成双向引用的绑定。
(3)记录头节点的first引用 指向 新节点(新节点的prev节点为null,可以继续连接后续新节点),向前移一步。
接着上面,继续向尾节点追加第三个节点(addLast),存储数据:3
(1)创建新节点,新节点的prev引用 指向 尾节点,成为链表中的一员。
(2)尾节点的next引用 指向 新节点,这一步完成双向引用绑定。
(3)记录尾节点的last引用 指向 新节点(新节点的next节点为null,可以继续连接后续新节点),向后移一步。
思路理清楚后,转化成代码:
/**
* 向链表头部追加节点
*
* @return
*/
public boolean addFirst(T val) {
// 临时变量 获取当前first头节点的引用指向
Node<T> f = first;
// 创建新节点
// prev:null,用来指向后续的新节点
// val:要存储的数据
// next:存放头节点的引用,将新节点的next节点 --> 头节点(相当于在头部节点前追加了一个元素)
Node<T> newNode = new Node<>(null, val, f);
// 判断尾节点是否为空
if(last == null) {
// 证明这个节点在链表当中是第一个加进来的(第一个加进来的既是头也是尾)
last = newNode;
} else {
// 新节点 <-- 头节点的prev引用(这一步完成双向引用绑定)
f.prev = newNode;
}
// 头节点的引用指向新节点
// first的prev节点引用是null的,prev节点引用会指向后续的新节点
first = newNode;
// 节点数量+1
size++;
return true;
}
/**
* 向链表尾部追加节点
*
* @return
*/
public boolean addLast(T val) {
// 临时变量 存储尾节点的引用
Node<T> l = last;
// 创建新节点
// prev:存放尾节点的引用,尾节点 <-- 新节点的prev节点(新节点的prev节点指向尾节点,相当于往尾部追加了一个新节点)
// val:要存储的数据
// next:null,用来指向后续的新节点
Node<T> newNode = new Node<>(l, val, null);
// 判断头节点是否为空
if(first == null) {
// 证明这个节点在链表当中是第一个加进来的(第一个加进来的既是头也是尾)
first = newNode;
} else {
// 尾节点的next引用 --> 新节点(这一步完成双向引用绑定)
l.next = newNode;
}
// 改变尾节点在链表中的引用位置
// 尾节点引用 指向 新节点引用
last = newNode;
// 节点数量+1
size++;
return true;
}
删除节点
删除节点主要有三种情况:
(1)删除的节点是头节点。
(2)删除的节点是尾节点。
(3)删除的节点是俩个节点之间的节点。
现在链表中有5个节点。
删一个头节点,该节点存储的数据:4
cur:当前被删除的节点。
(1)记录头节点的first引用 指向 cur的next节点。
(2)cur的next节点的prev引用 指向 cur前面的null节点。
(3)cur的next引用 指向 null,这样节点就从链表中删了(更确切的说是节点的next和prev引用都为null,然后被回收了)。
(分清next引用与节点)
接着上面,继续删除一个尾节点,该节点存储的数据:5
cur:当前被删除的节点。
(1)记录尾节点的last引用 指向 cur的prev节点。
(2)cur的prev节点的next引用 指向 cur后面的null节点。
(3)cur的prev引用 指向 null,从链表中移除,完成删除。
接着上面,继续 删除俩个节点之间的节点,该节点存储的数据:1
cur:当前被删除的节点。
(1)cur的 prev节点的next引用 指向 cur的 next节点。
(2)cur的 prev引用 指向 null。
(3)cur的 next节点的prev引用 指向 cur的 prev节点。
(4)cur的 next引用 指向 null,完成删除。
思路理清楚后,转化成代码:
/**
* 删除节点
*
* @param cur 当前要删除的节点
* @return
*/
public T remove(Node<T> cur) {
T curVal = cur.val;
// 获取当前节点的前一个节点
Node<T> prev = cur.prev;
// 获取当前节点的后一个节点
Node<T> next = cur.next;
if (prev == null) {
// 证明是头节点
// 记录头节点的引用 改变为 当前节点的后一个节点
first = next;
} else {
// 证明当前节点 前面有节点
// 当前节点前一个节点 的next引用 指向 当前节点的后一个节点
prev.next = next;
// 当前节点的prev引用 指向 null
cur.prev = null;
}
if (next == null) {
// 证明是尾节点
// 记录尾节点的引用 改变为 当前节点的前一个节点
last = prev;
} else {
// 证明当前节点 后面有节点
// 当前节点后一个节点 的prev引用 指向 当前节点的前一个节点
next.prev = prev;
// 当前节点的next引用 指向 null
cur.next = null;
}
// 当前节点的值置为空
cur.val = null;
// 节点数量减一
size--;
return curVal;
}
查找相对简单,这里就不继续展开了。
如有错误,欢迎指正!
码字不易,觉得不错点个赞再走呗!