🍵前言:
好了,栈的博客只是一个小小的热身,现在才是真正的想要展现给大家的,Java数据结构之双向链表,之前,我们都了解过了单向链表,知道了单向链表的增删插改遍历等操作,了解了它们什么时候需要找前一个元素,什么时候不需要,但是双向链表和前面的有所不同,让我们开始吧!
我们将从以下几点开始阐述:
双向链表的概述 🔜 单双链表之间的区别 🔜 双向链表的实现思路 🔜 双向链表的代码实现与分析 🔜 结论
喜欢博主的话,那就喜欢吧,不装了,摆烂了
往期精彩:
下一次博客发出的时间可能是一个星期后了,应为博主要写排序算法全解,篇幅超长,敬请期待!!!
🍘双向链表的概述
🥟双向链表
- 双向链表是基于单向链表的一个升级版链表,他也是一个有序列表
- 双向链表可以有头节点,也可以没有头节点
🥣节点
- 节点有三大属性,分别是next域,previous域,data域
- next放下一个节点的内存地址,属于has a 的关系
- pre放上一个节点的内存地址,属于has a 的关系
🥨单双链表之间的区别
- 单向链表只能从头节点开始遍历单向链表,而双向链表可以双向遍历
- 单向链表在删除元素时,需要找到待删除节点的上一个节点,非常的不方便
- 双向链表在删除元素时,可以直接找到待删除的节点进行删除即可
🍿双向链表的实现思路
- 创建一个双向链表的节点类,该节点中应该有(next,pre,data)(有参构造+无参构造)
- 创建一个双向链表类,用于管理双向链表,应该有节点属性与其对应的构造方法
- 添加增删插改遍历的方法
注意:本双向链表的创建以水浒英雄的信息的基础上进行创建
🍦双向链表的代码实现与分析
-
创建一个节点类
public int no;
public String name;
public String nickName;
public HeroNodeD next;
public HeroNodeD pre;
public HeroNodeD() {
}
public HeroNodeD(int no,String name,String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
public String toString() {
return "好汉的排行[" + no + "] 名字:" + name + " 称谓:" + nickName;
}
代码分析:
- name,nickName,no都是data数据,而next存放的是下一个节点的内存地址,pre存放的是上一个节点的内存地址
- 无参构造的目的是创建一个头节点,因为头节点里面的data大多数情况下都为空
- 重写toString方法,为了后期输出节点对象
图解:
-
创建一个双向链表类,用于管理双向链表
private HeroNodeD head;
public DoubleLinkedList() {
head = new HeroNodeD();
}
代码分析:
- head为头节点属性,需要通过构造方法给他new一个头节点
-
创建一个添加节点的方法
public void add(HeroNodeD nodeD) {
var cur = head;
while(cur.next != null) {
cur = cur.next;
}
cur.next = nodeD;
nodeD.pre = cur;
}
代码分析:
- 由于不确定双向链表是否为空 ,所以辅助引用需要用head赋值,否则可能出现空指针异常
- 通过循环,我们可以找到最后一个节点
- 添加节点的时候,记得pre和next都要赋值
- 补充:双向链表的头节点是整个链表的灵魂,不能直接使用,需要用一个辅助引用
图解:
-
创建一个删除节点的方法
public void del(int no) {
if(head.next == null) {
System.out.println("链表为空,无法删除");
return;
}
var cur = head.next;
boolean flag = false;
while(true) {
if(cur == null) {
break;
}
if(cur.no == no) {
flag = true;
break;
}
cur = cur.next;
}
if(flag) {
cur.pre.next = cur.next;
if (cur.next != null)
cur.next.pre = cur.pre;
return;
}
System.out.println("无此节点,无法删除");
}
代码分析:
- 第一步是判断一下双向链表是否为空,若为空直接退出(校验思想)
- 若第一个判断为false,说明双向链表不为空,可以给辅助引用赋值head,next即第一个节点
- 打一布尔标记,默认该节点不存在
- 建立循环,如果cur=null,说明已经遍历完链表,直接退出
- 如果cur.no = no 说明找到了待删除的节点,我们可以修改布尔标记并退出,否则继续往下找
- 根据布尔标记做出修改
图解:
-
创建一个插入节点的方法
public void insert(HeroNodeD newNode) {
if(head.next == null) {
head.next = newNode;
newNode.pre = head;
return;
}
boolean flag = false;
var cur = head;
while(true) {
if(cur.next == null) {
break;
}
if(cur.next.no > newNode.no){
break;
} else if(cur.next.no == newNode.no){
flag = true;
break;
}
cur = cur.next;
}
if(!flag) {
newNode.next = cur.next;
newNode.pre = cur;
if(cur.next != null){
newNode.next.pre = newNode;
}
cur.next = newNode;
return;
}
System.out.println("已有此节点,无法插入");
}
代码分析:
- 先判断双向链表是否为空,若为空直接在后面添加即可
- 打一布尔标记,默认插入的位置不存在
- 插入节点的时候仍要找到该节点的上一个节点,所以,辅助引用应该被赋值为head
- 循环,当该节点的next域为空时,直接插入到最后,直接退出
- 如果该节点的下一个节点的no大于所给的no,说明这两个节点之间可以插入,直接退出,如果该节点的下一个节点的no等于所给no,说明无法插入,该节点已经存在,,应该改布尔标记并直接退出,否则跳入下一个节点
- 根据布尔标记进行插入
图解:
-
创建一个更新节点的方法
public void update(HeroNodeD newNode) {
if(head.next == null) {
System.out.println("链表为空,无法更新数据");
return;
}
boolean flag = false;
var cur = head.next;
while(true) {
if(cur == null) {
break;
}
if(cur.no == newNode.no){
flag = true;
break;
}
cur = cur.next;
}
if(flag) {
cur.name = newNode.name;
cur.nickName = newNode.nickName;
return;
}
System.out.println("无该排名节点,无法更新");
}
代码分析:
- 先判断双向链表是否为空,若为空直接结束,无法修改任何的节点(校验意识)
- 打一布尔标记,默认该节点不存在
- 若第一个条件为false,说明双向链表不为空,所以可以将第一个节点赋予辅助引用
- 若cur = null.说明所有的节点都遍历过,可以直接break,否则空指针异常
- 若对应的no相同,说明找到了节点,将布尔标记更改并退出,否则跳入下一个节点
- 根据布尔标记进行修改
-
创建一个遍历节点的方法
public void print() {
if(head.next == null) {
System.out.println("链表为空,无法遍历");
}
var cur = head.next;
while(cur != null) {
System.out.println(cur);
cur = cur.next;
}
}
代码分析:
- 先判断双向链表是否为空,若为空则直接跳出,若不为空辅助引用应该被赋予第一个节点
- 我相信大家都可以看得懂的啦
完整代码
package datastructure.chapter01.linked_list.double_linked_list;
public class DoubleLinkedList {
private HeroNodeD head;
public HeroNodeD getHead() {
return head;
}
public DoubleLinkedList() {
head = new HeroNodeD();
}
public void add(HeroNodeD nodeD) {
var cur = head;
while(cur.next != null) {
cur = cur.next;
}
cur.next = nodeD;
nodeD.pre = cur;
}
public void del(int no) {
if(head.next == null) {
System.out.println("链表为空,无法删除");
return;
}
var cur = head.next;
boolean flag = false;
while(true) {
if(cur == null) {
break;
}
if(cur.no == no) {
flag = true;
break;
}
cur = cur.next;
}
if(flag) {
cur.pre.next = cur.next;
if (cur.next != null)
cur.next.pre = cur.pre;
return;
}
System.out.println("无此节点,无法删除");
}
public void print() {
if(head.next == null) {
System.out.println("链表为空,无法遍历");
}
var cur = head.next;
while(cur != null) {
System.out.println(cur);
cur = cur.next;
}
}
public void update(HeroNodeD newNode) {
if(head.next == null) {
System.out.println("链表为空,无法更新数据");
return;
}
boolean flag = false;
var cur = head.next;
while(true) {
if(cur == null) {
break;
}
if(cur.no == newNode.no){
flag = true;
break;
}
cur = cur.next;
}
if(flag) {
cur.name = newNode.name;
cur.nickName = newNode.nickName;
return;
}
System.out.println("无该排名节点,无法更新");
}
public void insert(HeroNodeD newNode) {
if(head.next == null) {
head.next = newNode;
newNode.pre = head;
return;
}
boolean flag = false;
var cur = head;
while(true) {
if(cur.next == null) {
break;
}
if(cur.next.no > newNode.no){
break;
} else if(cur.next.no == newNode.no){
flag = true;
break;
}
cur = cur.next;
}
if(!flag) {
newNode.next = cur.next;
newNode.pre = cur;
if(cur.next != null){
newNode.next.pre = newNode;
}
cur.next = newNode;
return;
}
System.out.println("已有此节点,无法插入");
}
}
🍔结论
本篇博客主要阐述了双向链表的创建与其功能的完善,特此,我来总结一下必须掌握的几点
双向链表什么时候找前一个节点什么时候找当前节点
双向链表删除节点的情况如何
防止空指针异常的举措
下一站:一万年以后