Java数据结构与算法(四)双向链表
前言
学习双向链表之前需要了解单链表,在我的Java数据结构与算法(三)单链表中有介绍到,接下来说下单链表和双向链表的区别:
- 单向链表查找的方向只能是一个方向,而双向链表可以向前或者向后查找
- 单链表不能进行自我删除,需要借助辅助节点,而双向链表可以自我删除
- 双向链表比单链表的节点的定义多一个pre变量,用于指向前一个节点
代码实现
首先定义节点类,每一个对象就是一个节点,pre指向前一个节点,next指向后一个节点
//定义一个HeroNode,每个对象就是一个节点
class HeroNode {
public int no;
public String name;
public String nickedName;
public HeroNode pre;//指向前一个节点
public HeroNode next;
public HeroNode(int no, String name, String nickedName) {
this.no = no;
this.name = name;
this.nickedName = nickedName;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickedName='" + nickedName + '\'' +
'}';
}
}
写基本的增删查改方法:
定义头节点
//创建一个双向链表的类
class DoubleLinkedList{
private HeroNode head = new HeroNode(0,"","");
public HeroNode getHead(){
return head;
}
}
遍历整个链表的方法,先判断链表是否为空,空则直接返回;遍历的时候要借助一个辅助节点temp来帮忙遍历,因为头节点本身是不能动的;写一个while循环,每一次循环打印出节点的信息,打印完需要将节点后移,当temp为空的时候就表示遍历完了,可以退出循环。
//遍历双向链表
//显示链表
public void list(){
//判断链表是否为空
if (head.next == null){
System.out.println("链表为空");
return;
}
//头节点不能动
HeroNode temp = head.next;
while (true){
//是否到链表最后
if (temp == null){
break;
}
//输出节点信息
System.out.println(temp);
//将next后移
temp = temp.next;
}
}
增加一个节点的方法,参数传入一个节点对象,先进行遍历,遍历找到链表的最后就可以退出循环;退出循环后就可以添加节点了,将next指向新添加进来的节点,temp.next = heroNode,注意的是这是双向链表,还需要将新添加进来的节点的pre指向temp。
//添加子节点
public void add(HeroNode heroNode){
HeroNode temp = head;//引用了head对象,辅助head
//遍历链表,找到最后
while (true){
//找到链表的最后
if (temp.next == null){
break;
}
//如果没有找到最后,那就将temp后移
temp = temp.next;
}
//当退出了while循环,temp已经找到了最后一个
//形成一个双向链表的最后
temp.next = heroNode;
heroNode.pre = temp;
}
按照no有序地添加链表,传入一个节点对象参数,设置辅助节点和标识,标识是用于查看是否有no重复的情况;接下来进行遍历循环,如果下一个节点的no大于要插进来的节点的no,那就代表找到了位置,位置就是temp的下一个,这时候退出循环;如果找到有重复no的情况,那就将flag置为true,退出循环;退出循环后进行判断,如果flag为true那就打印有重复的情况发生,否则就进行添加操作,添加的时候也需要分情况:如果添加进来的节点的位置后面还有其他节点的话,那就需要将heroNode.next = temp.next,并且temp.next.pre = heroNode,然后最后就将temp.next = heroNode和heroNode.pre = temp;如果添加进来的节点是最后一个位置时,就直接将temp.next = heroNode和heroNode.pre = temp就行了。
//有序添加链表
public void addByOrder(HeroNode heroNode){
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){//此时的情况是链表为空或者已经找到了最后一个链表
break;
}
if (temp.next.no > heroNode.no){//如果下一个节点的no要大于插进来的no,那就找到位置了,直接结束循环,插入的位置就在temp的下一个位节点
break;
}else if (temp.next.no == heroNode.no){//如果下一个节点的no等于要插进来的no,那就说明有重复的情况
flag = true;
break;
}
temp = temp.next;//temp后移
}
if (flag){
System.out.println("此编号的英雄已存在,不能重复添加!");
}else {
//插入要分情况,第一种情况是要插入的节点是最后一个节点,第二种情况是要插入的节点后面还有其他的节点
//经过分析,由于两种情况都要经过temp.next = heroNode;heroNode.pre = temp;所以就抽取出来
if (temp.next!=null){//第二种情况,要插入的节点后面还有其他节点
heroNode.next = temp.next;
temp.next.pre = heroNode;
}
//如果要插入的节点后面没有其他节点的话就正常插入
temp.next = heroNode;
heroNode.pre = temp;
}
}
修改一个节点的内容,参数传入一个节点对象,首先判断链表是否为空;设置一个辅助节点,设置一个用于标识是否找到需要修改的节点的标志;接下来进行遍历,遍历的时候需要进行判断temp的no是否和传进来的no相等,如果相等就将flag置为true,并退出循环,否则就继续进行下一遍循环,直到遍历完整个链表才退出循环;当退出循环后需要进行,判断flag是否为true,如果为true就代表找到了需要修改的节点,然后就进行修改就行了,否则就输出没有找到。
//修改一个节点的内容
//根据no来修改
public void update(HeroNode heroNode){
if (head.next==null){
System.out.println("链表为空");
return;
}
//找到需要修改的节点,根据no
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点
while (true){
if (temp == null){
break;//到链表最后还没找到
}
if (temp.no==heroNode.no){
flag = true;
break;
}
temp = temp.next;
}
//根据flag判断是否找到
if (flag){
temp.name = heroNode.name;
temp.nickedName = heroNode.nickedName;
}else {//没有找到
System.out.println("没有找到");
}
}
删除节点方法,传入参数no,这个方法需要做的东西首先也是先找到需要删除的节点,和上面修改是差不多的;找到之后就将temp.pre.next = temp.next,然后需要进行判断,判断要删除的节点是否为最后一个节点,如果不是那就还需要将temp.next.pre = temp.pre。
//双向链表删除节点
//删除节点
//对于双向链表可以直接找到想要删除的节点,找到后自我删除
public void delete(int no){
//判断是否为空
if (head.next == null){
System.out.println("链表为空无法删除");
return;
}
HeroNode temp = head.next;
boolean flag = false;//表示是否找到要删除的前一个节点
while (true){
if (temp==null){//已经找到最后的节点的next=空
break;
}
if (temp.no == no){
flag = true;//找到了
break;
}
temp = temp.next;//后移
}
//判断flag
if (flag){//找到
temp.pre.next = temp.next;
//下面一行代码有问题
//如果是最后一个节点,就不需要执行下面这一行代码,否则会空指针
//所以要加一个条件判断,判断要删除的节点是否为最后一个节点
if (temp.next!=null){
temp.next.pre = temp.pre;
}
}else {
System.out.println("没找到该节点");
}
}
总结
双向链表的增删查改和单链表的思路是差不多的,不一样的地方就是需要考虑pre这个指针,在分析的时候最后画一下图就比较清晰了。