学 无 止 境 , 与 君 共 勉 。
相关系列
简介
链表是一种将数据存储在链节点中的数据结构。在链表中,每个数据项都包含在一个链节点中。每个链节点中除了数据项外,还包含了对其他链节点的引用。需要存储多少个数据,就生成多少个链节点,这些链节点点之间相互引用。
单链表
在单链表中,包含了对首个链表节点的引用(可以称为first),每个链表节点都包含了一个对下一个链节点引用的字段(可以称为next)。

单链表的建立有头插法和尾插法两种
头插法:
将链表的右端看成是固定的,插入时,将首个链节点的引用指向要插入的节点,将插入节点的对下个节点的引用next指向原来最左边的节点;

尾插法:
将链表的左端看成是固定的,每次插入时,将原先最右边节点的next指向新插入的节点;

双端链表
相对单链表,在双端链表中,还包含了对最后一个链表节点的引用(可以称为last),可以同时实现在头部和尾部插入新的节点。插入方式和单链表类似。需要注意的是,在尾部插入节点数据后,需要将对最后一个节点的引用last指向新插入的节点。

双向链表
前面讲的那些链表都是只支持向前遍历,只能获取的下一个节点的信息,无法回过去获取上一个节点的信息。双向链表解决了这个问题,它的链表节点中除了包含对下一节点的引用next外,还包含了对上一节点的引用prev。但是相应的每次插入或者删除的时候需要处理4个链节点的引用,链节点的占用空间也变大了一点。双向链表可以不是双端链表,这里我们用双向双端展示。

插入
将要插入节点的prev指向前一个节点,next指向后一节点,将前一节点的next指向插入节点,将后一节点的prev指向插入节点;

- 在头部插入
/**
* 在首位插入
* 空的,首节点和末节点都指向新节点;
* 新节点的next指向原来首节点,
* 原来首节点存在的prev指向新节点
*
* @param data 数据
*/
public void insertFirst(E data) {
// 新节点 next 为原来第一个节点,prev 为 null
Node<E> newNode = new Node<E>(null, data, this.first);
// 空的,需要将最后的节点也指向新的节点
if (this.first == null) {
this.last = newNode;
} else {
this.first.setPrev(newNode);
}
this.first = newNode;
this.size++;
}
- 在尾部插入
/**
* 在末尾插入
* 空的,首节点和末节点都指向新节点;
* 新节点的prev指向原来尾节点,
* 原来尾节点存在的next指向新节点
*
* @param data 数据
*/
public void insertLast(E data) {
// 新节点 prev 为原来最后一个节点,next 为 null
Node<E> newNode = new Node<E>(this.last, data, null);
if (this.first == null) {
this.first = newNode;
} else {
this.last.setNext(newNode);
}
this.last = newNode;
this.size++;
}
- 在中间插入
/**
* 在目标节点前添加
*
* @param data 数据
* @param tagNode 目标节点
*/
public void insertBefore(E data, Node<E> tagNode) {
// 前置节点
Node<E> prevNode = tagNode.getPrev();
// 新节点的前置节点为目标节点的前置节点,后置节点为目标节点
Node<E> newNode = new Node<E>(prevNode, data, tagNode);
// 重置next节点
prevNode.setNext(newNode);
// 重置prev节点
tagNode.setPrev(newNode);
this.size++;
}
删除
将要删除节点的前一节点的next指向要删除节点的后一节点,将要删除节点的后一节点的prev指向要删除节点的前一节点;

- 删除头部
/**
* 删除头部数据
* 只有一个节点的删除后,将最后节点引用设为null
* 首个节点的引用设为原来第二个节点
* 原来第二个节点的前置节点设为null
*/
public void delFirst() {
Node<E> fNode = this.first;
if (fNode == null) {
System.out.println("删除失败,这是个空链表!!!");
return;
}
Node<E> sNode = fNode.getNext();
fNode.setData(null);
// 去除引用,用于GC
fNode.setNext(null);
// 重置首个节点
this.first = sNode;
// 链表只有1个节点的,删除后最后一个节点指向null
if (sNode == null) {
this.last = null;
} else {
// 重置prev节点
sNode.setPrev(null);
}
this.size--;
}
- 删除尾部
/**
* 删除尾部
* 只有一个节点的删除后,将首个节点引用设为null;
* 最后一个节点的引用设为原来倒数第二个节点
* 原来倒数第二个节点的后置节点设为null
*/
public void delLast() {
Node<E> lNode = this.last;
if (lNode == null) {
System.out.println("删除失败,这是个空链表!!!");
return;
}
Node<E> prevNode = lNode.getPrev();
lNode.setData(null);
lNode.setPrev(null);
this.last = prevNode;
if (prevNode == null) {
this.first = null;
} else {
prevNode.setNext(null);
}
this.size--;
}
- 删除中间
public void del(Node<E> delNode) {
Node<E> prevNode = delNode.getPrev();
Node<E> nextNode = delNode.getNext();
// 先处理左边2个引用关系
if (prevNode == null) {
// 删除首个
this.first = nextNode;
} else {
prevNode.setNext(nextNode);
delNode.setPrev(null);
}
// 再处理右边2个引用关系
if (nextNode == null) {
// 删除尾部节点
this.last = prevNode;
} else {
nextNode.setPrev(prevNode);
delNode.setNext(null);
}
delNode.setData(null);
this.size--;
}
小结
数据存在于链表节点中;
节点中包含了对其他节点的引用;
插入、新增时调整前后节点的引用关系;
通过当前节点对下一节点或者上一节点的引用去遍历数据;
访问源码
本系列所有代码均上传至Github上,方便大家访问
日常求赞
创作不易,如果各位觉得有帮助,求点赞支持
求关注
