一)链表简介
链表概念:链表是一种物理存储单元上非连续、非顺序的存储结构,也就是说链表存储的数据不一定是有序的。
链表结构:链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
双链表:每一个结点中,包含一个数据域,两个指针域,一个指针域指向上一个结点,一个指针域指向下一个结点。
双链表图解:头部结点(first),上一结点指针域(prev),数据域(data)、下一结点指针域(next),尾部结点(last)。头部first结点的prev指向为null,尾部last结点的next指向null
二)双链表
第一步:初始化双链表结构
/**
* 双链表
* @author ouyangjun
*/
public class DoubleLinkedList<E> {
/** 双链表结构 */
static final class Node<E> {
E data; // 数据域
Node<E> prev; // 指针域, 指向上一个结点
Node<E> next; // 指针域, 指向下一个结点
Node(E x) {data = x;}
}
transient Node<E> first; // 指向双链表头部节点
transient Node<E> last; // 指向双链表尾部节点
transient int count; // 双链表结点数量
}
第二步:添加结点
场景一:在双链表头部添加结点。新添加结点a,把结点a的next指向结点a1,把结点a1的prev指向结点a,最后把新结点a作为新的头部first结点。
/** 在双链表头部添加结点 */
public boolean addFirst(E e) {
if (e == null) throw new NullPointerException();
Node<E> newNode = new Node<E>(e); // 新结点
Node<E> f = first; // 声明一个临时指针指向first
newNode.next = f; // 把新结点的next指向头部first结点
first = newNode; // 把新结点作为新的头部first结点
if (last == null) {
last = newNode; // 当尾部last结点为null时, 新结点即是头部结点也是尾部结点
} else {
f.prev = newNode; // 把原先的头部first结点的prev指向新结点
}
++count; // 累计双链表结点数量
return true;
}
场景二:在双链表尾部添加结点。新增结点a,把结点a的prev指向结点a4,把结点a4的next指向结点a,最后把新结点a作为新的尾部last结点。
/** 在双链表尾部添加结点 */
public boolean addLast(E e) {
if(e == null) throw new NullPointerException();
Node<E> newNode = new Node<E>(e); // 新结点
Node<E> l = last; // 声明一个临时指针指向last
newNode.prev = l; // 把新结点的prev指向尾部last结点
last = newNode; // 把新结点作为新的尾部last结点
if (first == null) {
first = newNode; // 当头部first结点为null时, 新结点即是头部结点也是尾部结点
} else {
l.next = newNode; // 把原先的尾部last结点的next指向新结点
}
++count; // 累计双链表结点数量
return true;
}
场景三:在指定下标位置添加结点。在结点a2和结点a3之间新增结点a,把结点a的prev指向结点a2,把结点a结点next指向结点a3,把结点a2的next指向结点a,把结点a3的prev指向结点a。需考虑在头部、在中间、在尾部添加的情况。
/** 在指定下标位置添加结点 */
public void add(int index, E e) {
if(index < 0 || index > count) throw new IllegalArgumentException();
if(e == null) throw new NullPointerException();
if (index == 0) { // 在头部first结点添加
addFirst(e);
} else if (index == count) { // 在尾部last结点添加
addLast(e);
} else {
Node<E> newNode = new Node<E>(e); // 新结点
int position = 0; // 从头开始
Node<E> f = first; // 声明一个临时指针指向first
while (f != null) {
if (index == position++) { // 查找对应的插入位置
Node<E> p = f.prev; // 记录当前结点的prev
newNode.prev = p; // 把新节点的prev指向p
newNode.next = f; // 把新节点的next指向f
p.next = newNode; // 把p的next指向新结点
f.prev = newNode; // 把当前结点的prev指向新结点
++count;
break;
} else {
f = f.next;
}
}
}
}
第三步:删除结点
场景一:从双链表头部first删除结点。把原先头部first结点a1的next指向null,相当于断开结点a1,再把结点a2的prev指向null,并把结点a2作为新的头部first结点。
/** 从双链表头部first删除结点 */
public boolean removeFirst() {
Node<E> f = first;
if(f == null) return false;
Node<E> n = f.next; // 获取头部first结点的next结点
f.data = null; // 把头部first结点的data置null, 相当于回收
f.next = f; // 把头部first结点断开
first = n; // 把n结点作为新的头部结点
if (n == null) { // 判断是否需要清除结点n的prev
last = null;
} else {
n.prev = null;
}
--count; // 累减双链表结点数量
return true;
}
场景二:从双链表尾部last删除结点。把原先尾部last结点a4的prve指向null,相当于断开结点a4,再把结点a3的next指向null,并把结点a3作为新的尾部last结点。
/** 从双链表尾部last删除结点 */
public boolean removeLast() {
Node<E> l = last;
if(l == null) return false;
Node<E> p = l.prev; // 获取尾部last结点的prev结点
l.data = null; // 把尾部last结点的data置null, 相当于回收
l.prev = l; // // 把尾部last结点断开
last = p; // 把p结点作为新的尾部结点
if (p == null) { // 判断是否需要清除结点p的next
first = null;
} else {
p.next = null;
}
--count; // 累减双链表结点数量
return true;
}
场景三:删除双链表中指定data的结点。删除结点a2,把结点a2的prev和next指向结点都置null,相当于断开,把结点a1的next指向结点a3,把结点a3的prev指向结点a1。需考虑在头部、在中间、在尾部删除的情况。
/** 删除双链表中指定data的结点 */
public boolean remove(E e) {
if(e == null) throw new NullPointerException();
Node<E> deleteNode = new Node<E>(e); // 需删除的结点
Node<E> f = first;
Node<E> l = last;
if (f == null || deleteNode.data.equals(f.data)) { // 删除头部结点
return removeFirst();
} else if (l == null || deleteNode.data.equals(l.data)) { // 删除尾部结点
return removeLast();
} else {
while (f != null) {
if (deleteNode.data.equals(f.data)) {
Node<E> p = f.prev; // 获取当前节点的prev
Node<E> n = f.next; // 获取当前节点的next
p.next = n; // 把p节点的next指向n
n.prev = p; // 把n节点的prev指向n
f.data = null; // 置null
f.prev = f; // 指向本身,相当于断开
f.next = f; // 指向本身,相当于断开
--count;
break;
}
f = f.next;
}
}
return true;
}
第四步:打印双链表结点信息
/** 打印双链表结点信息 */
public void printDoubleLinkedList() {
System.out.println("----从头部first结点打印----begin");
Node<E> f = first;
while (f != null) {
System.out.println(f.data);
f = f.next;
}
System.out.println("----从头部first结点打印----end");
System.out.println("----从尾部last结点打印----begin");
Node<E> l = last;
while (l != null) {
System.out.println(l.data);
l = l.prev;
}
System.out.println("----从尾部last结点打印----end");
}
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!