目录
单向链表的局限
单向链表虽然有很多的优点和好处,但也是有一些局限性的,有些功能是单向链表很难实现或实现不了的,这时候就需要我们的双向链表出场了!
单向链表对比双向链表存在的局限可以大概分为以下两种:
- 单向链表查找的方向只能是一个方向,而双向链表则可以向前或向后查找。
- 单向链表不能实现自我删除,需要辅助节点找到待删除节点的前一个节点。而双向链表则可以实现自我删除。
双向链表的原理
双向链表与单向链表的唯一区别就是双向链表的节点比单向链表的节点中多了一个指向其前一个节点的指针pre:
因为双向链表的遍历(显示)和修改操作和单向链表是一样的,所以今天我就只该大家演示双向链表的添加和删除操作了。
初始化工作
双向链表的节点我们只需要在单向链表的基础上,在增加一个指向其前一个节点的前驱指针即可:
// 定义节点
class Child2{
int no;
String name;
String sex;
Child2 next; // 指针,指向下一个节点
Child2 pre; // 指针,指向前一个节点
// 构造函数
public Child2(int no, String name, String sex){
this.no = no;
this.name = name;
this.sex = sex;
}
// 为了显示方便,我们需要重写toString方法
@Override
public String toString(){
return "child [no = " + no + ", name = " + name + ", sex = " + sex + "]";
}
}
双向链表的定义与单向链表是一样的:
// 定义双向链表
class DoubleLinkedList {
// 先初始化一个头节点
Child2 head = new Child2(0, "", ""); // 头节点不能动,也不存放任何数据
}
双向链表的添加操作(添加到末尾)
1、思路分析:
双向链表的第一种添加操作的思路与单向链表的基本一样,不同的是多出一步把新节点的pre指向辅助指针p指向的节点:
2、代码实现:
// 向双向链表中添加节点(添加到末尾)
public void add(Child2 child2){
Child2 p = head; // p 为辅助指针,帮助我们遍历链表
while(true){
if(p.next == null){
break;
}
p = p.next;
}
p.next = child2;
child2.pre = p;
}
双向链表按序号添加节点
1、思路分析:
这个操作与单向链表对比就稍微有点儿不一样了,因为是双向链表(能直接找到前驱),所以我们可以直接找到编号比待加入的节点的编号大的节点:
然后我们要做的就是将p的前一个节点的next指针指向新节点,再将新节点的pre指针指向p的前一个节点,然后再是将p的pre指针指向新节点,再将新节点的next指针指向p:
这里特别需要注意的是:
如果新节点的编号比链表中已有的任何一个节点的编号都要大或者加入的是第一个节点,那么加入这个新节点就相当于在末尾添加,添加的方法与第一种方法相同。而如果是添加第一个节点的时候再使用“p.pre.next = 新节点”就会出现空指针异常。所以这个方法最要分4种情况分析。
2、代码实现:
// 按序号添加节点
public void add2(Child2 child2){
Child2 p = head;
boolean flag = false; // 标志待加入的节点是否已存在,默认为false
while(true){
if(p.next == null){
break;
}
if(p.no > child2.no){ // 找到了
break;
}
if(p.no == child2.no){
flag = true;
break;
}
p = p.next;
}
if(flag) {
System.out.println("待插入的节点已经存在,不能重复添加");
}else if ((p.next == null && p.no < child2.no) || head.next == null){
p.next = child2;
child2.pre = p;
}else{
p.pre.next = child2;
child2.pre = p.pre;
p.pre = child2;
child2.next = p;
}
}
双向链表的删除操作
1、思路分析:
因为是双链表,可以直接找到节点的前驱和后继,所以我们可以实现自删除操作,我们可以直接找到要删除的节点:
之后我们要做的就是将p的前一个节点的next指针指向p的后一个节点,将p的后一个节点的pre指针指向p的前一个节点:
即:p.pre.next = p.next 和 p.next.pre = p.pre
这里也需要注意:
如果要删除的节点刚好是最后一个节点,就不能用上面这种方法删除,因为最后一个节点的next为null
2、代码实现:
// 根据编号删除节点
public void delete(int no){
Child2 p = head.next;
boolean flag = false; // 标志是否找到待删除的节点
if (head.next == null) {
System.out.println("链表为空~");
return;
}
while (true) {
if (p == null) {
break;
}
if (p.no == no) {
flag = true;
break;
}
p = p.next;
}
if (flag) {
p.pre.next = p.next;
p.next.pre = p.pre;
}else if(p.next == null){
p.pre.next = null;
p.pre = null;
}else{
System.out.println("待删除的节点没有找到");
}
}
后语
好了,今天的分享就到这里了,学习变成最重要的是写,大家一定要自己多多动手写代码,再见!