1.简介
在使用带头节点的单向链表查找时查找的方向只能是一个方向,而双向链表可以向前或向后查找。例如单向链表只能查到一个节点的下一个节点,但是双向链表不仅可以查到下一个节点,还可以查到上一个节点。
在删除节点时,单向链表不能自我删除,需要借助辅助节点temp,因为单向链表就能找到下一个节点,把这个节点删了就不能直接找到下一个节点了。而双向链表可以自我删除,反正可以找到前一个节点,也可以找到后一个节点,这个节点自己就可以实现删除自我,让自己上一个节点和下一个节点连起来。
比如单向链表长这样:一个next可以找到下一个节点
双向链表可以长这样:一个pre指向前一个节点,一个next指向后一个节点
它可以从前往后查找,也可以从后往前查找
2.思路
①遍历:在遍历时遍历方式和单链表一样,只是它可以向前查找,也可以向后查找
②添加:在添加节点时,默认添加到双向链表最后的情况,先找到双向链表最后的节点,找到以后先使temp.next=newHeroNode,再使newHeroNode.pre=temp。就是原来最后一个节点的下一个节点是新节点,新节点的前一个是原来最后一个节点。
③修改:和单向链表一样
④删除:比如删除编号为5的节点,先找到要删除的这个节点temp,然后使temp.pre.next=temp.next,再使temp.next.pre=temp.pre。这样遍历的时候就跳过要删除的节点了。
要注意:在删除最后一个节点时如果要找temp.next.pre就会找不到。所以如果temp.next==null时就不执行temp.next.pre=temp.pre这行代码。
这两行代码可以记成:等号前面是更长的,有next有pre,等号后面是前面的头和尾,没有中间的next或者pre。
temp.next.pre=temp.pre
temp.pre.next=temp.next
⑤考虑编号顺序添加节点:找到要添加位置的前一个节点temp,找到以后新节点.next=temp.next,判断temp.next是否为空,如果不为空就temp.next.pre=新节点。然后temp.next=新节点,新节点.pre=temp。
3.代码
package com.xjj.linkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
//测试
System.out.println("双向链表");
//创建节点
HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");
HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");
//创建双向链表
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.list();
doubleLinkedList.add(hero1);
doubleLinkedList.add(hero2);
doubleLinkedList.add(hero3);
doubleLinkedList.add(hero4);
doubleLinkedList.list();
//修改双向链表
System.out.println("修改双向链表");
HeroNode2 newHeroNode = new HeroNode2(2, "卢", "玉麒麟--");
doubleLinkedList.update(newHeroNode);
doubleLinkedList.list();
//删除
System.out.println("删除节点1");
doubleLinkedList.delete(1);//删除第一个节点
doubleLinkedList.list();
System.out.println("删除节点4");
doubleLinkedList.delete(4);//删除第四个节点
doubleLinkedList.list();
//按编号顺序添加
System.out.println("按编号顺序添加");
DoubleLinkedList doubleLinkedList1 = new DoubleLinkedList();
doubleLinkedList1.addByOrder(hero2);
doubleLinkedList1.addByOrder(hero4);
doubleLinkedList1.addByOrder(hero3);
doubleLinkedList1.addByOrder(hero1);
doubleLinkedList1.list();
}
}
class HeroNode2 {
public int no;
public String name;
public String nickname;
public HeroNode2 next;//指向下一个节点
public HeroNode2 pre;//指向上一个节点
//构造器
public HeroNode2(int hNo, String hName, String hNickname) {
this.no = hNo;
this.name = hName;
this.nickname = hNickname;
}
//重写toString来显示方法
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
class DoubleLinkedList {
private HeroNode2 head = new HeroNode2(0, "", "");
//返回头节点
public HeroNode2 getHead() {
return head;
}
//添加节点。不考虑编号顺序时,先找到当前链表的最后节点,再将这个节点的next指向新节点
public void add(HeroNode2 heroNode) {
//辅助遍历temp
HeroNode2 temp = head;
//遍历链表找到最后
while (true) {
if (temp.next == null) {//找到最后了就退出
break;
}
//没到最后就后移到下一个节点
temp = temp.next;
}
//退出while循环时temp就指向链表最后
//我们要添加新节点,就将这个最后节点的next指向节点,将节点的前一个指向temp
temp.next = heroNode;
heroNode.pre = temp;
}
//添加节点,考虑编号顺序。也就是插入到链表非末尾位置
public void addByOrder(HeroNode2 heroNode) {
//辅助变量位于添加位置的前一个节点
HeroNode2 temp = head;
boolean flag = false;//标识添加的编号是否存在。如果存在就不能添加了
while (true) {
if (temp.next == null) {
//已经在链表最后,要添加的节点可能已经在链表最后
break;
}
if (temp.next.no > heroNode.no) {
//temp下一个节点的编号大于要插入的节点编号,就是找到了,要在这个temp后面插入
break;
} else if (temp.next.no == heroNode.no) {
//编号已存在
flag = true;//编号存在
}
temp = temp.next;//后移
}
//判断编号是否已存在
if (flag) {
System.out.printf("准备插入的编号 %d 已经存在,无法添加\n", heroNode.no);
} else {
//新节点插入链表中temp的后面
heroNode.next = temp.next;//新节点下一个指向插入节点
if (temp.next != null) {
temp.next.pre = heroNode;
}
temp.next = heroNode;//插入节点后一个的前一个指向新节点
heroNode.pre = temp;//新节点前一个指向插入节点前一个
}
}
//根据编号修改节点信息,即编号不能修改
public void update(HeroNode2 newHeroNode) {
//判断链表是否为空
if (head.next == null) {
//如果链表为空就不遍历了
System.out.println("链表为空");
return;
}
//找到需要修改的节点
//辅助变量
HeroNode2 temp = head.next;
boolean flag = false;//判断是否找到该节点
while (true) {
if (temp == null) {
break;//已经遍历完链表
}
if (temp.no == newHeroNode.no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
//判断是否找到节点
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else {
System.out.printf("没有找到编号为 %d 的节点\n", newHeroNode.no);
}
}
//删除节点
//直接找到要删除的节点
public void delete(int no) {
//判断链表是否为空
if (head.next == null) {
//如果链表为空就不遍历了
System.out.println("链表为空");
return;
}
//找到需要修改的节点
//辅助变量
HeroNode2 temp = head.next;//直接指向head.next
boolean flag = false;//判断是否找到该节点
while (true) {
if (temp == null) {
break;//已经遍历完链表
}
if (temp.no == no) {
//找到待删除节点
flag = true;
break;
}
temp = temp.next;
}
//判断是否找到节点
if (flag) {
temp.pre.next = temp.next;//前一个的下一个直接变成下一个
//temp.next.pre=temp.pre;//下一个的前一个变成前一个。但是有风险
//如果删除最后一个节点,temp就没有next了。所以是删最后一个节点就不用执行下面的代码
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("没有找到编号为 %d 的节点\n", no);
}
}
//显示链表,看有没有成功
public void list() {
//判断链表是否为空,如果为空就不遍历了
if (head.next == null) {
System.out.println("链表为空");
return;
}
//不为空就遍历
//使用辅助变量来遍历
HeroNode2 temp = head.next;//这里指向的是下一个
while (true) {
//看是否到链表最后
if (temp == null) {
break;
}
//不为空就输出节点信息
System.out.println(temp);
//后移
temp = temp.next;
}
}
}
4.运行结果
5.提炼一下
①直接在链表末尾添加节点就是找到末尾节点temp,然后使temp.next=新节点,再使新节点.pre=temp
②在链表中间按编号顺序添加就是找到添加位置的前一个节点temp,找到以后新节点.next=temp.next,判断temp.next是否为空,如果不为空就temp.next.pre=新节点。然后temp.next=新节点,新节点.pre=temp。
②删除节点就遍历找到该节点temp后temp.next.pre=temp.pre,temp.pre.next=temp.next
③遍历以及修改节点与单链表的一样
加油加油^_^