双向链表
- 与单向链表的对比:
(1)、单向链表的查找只有一个方向,而双向链表可以向前和向后查找;
(2)、双向链表可以实现自我删除;而单链表删除一个节点必须要借助辅助节点,即需要删除节点的前一个节点。
- 双链表基本操作的实现思路:
(1)、遍历:方式和单链表一样,仅仅是多了双向查找的功能;
(2)、添加(到双链表最后):
1、首先找到双链表的最后一个节点;
2、temp.next = newHeroNode ; newHeroNode.pre = temp;(temp指向双链表的最后一个节点)
(3)、修改:思路与单链表一样,不需要改动;
(4)、删除(改动较多):
1、由于是双链表,故可采用自我删除;
2、自我删除,即直接找到要删除的节点(如temp),执行以下操作:
3、temp.pre.next = temp.next ; temp.next.pre = temp.pre;
双向链表定义的代码实现:
① 首先定义链表节点类:
class HeroNode2 {
public int no;
public String name;
public String nickName;
public HeroNode2 next; // 指向下一个节点,默认为null
public HeroNode2 pre; // 指向上一个节点,默认为null
// 构造器
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;
}
/*
链表的操作代码接着往下看
*/
}
方法1:遍历
定义一个temp指针,从头节点的下一个节点(首节点)起依次输出当前temp所指节点的内容。
// 遍历双向链表
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);// 由于toString方法已经重写,所以直接打印temp会很方便
temp = temp.next;
}
}
方法2:添加节点(heroNode2)到尾部:
① 不考虑节点编号的顺序:
定义一个temp指针,从头head开始一直移动到双向链表的尾部,然后执行添加节点的操作:
// 添加节点操作
public void add(HeroNode2 heroNode2) {
HeroNode2 temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
// 形成一个双向链表的操作
temp.next = heroNode2;
heroNode2.pre = temp;// 双向链表的插入
}
② 需要考虑节点编号的顺序:(有点难度)
定义一个temp指针,用于查找插入新节点的适当位置:
1、必须考虑到插入点在尾部与其他情况的不同;
2、当插入点编号存在时,此时选择不插入。
3、查找插入位置的方法由“双链表的遍历”深化而来
//第二种方式添加(有难度)
public void addOrder(HeroNode2 heroNode2){
HeroNode2 temp = head ;
while(true){
if(temp.next == null){
temp.next = heroNode2;
heroNode2.pre = temp ;
break ;
}else if (temp.next.no > heroNode2.no){//表示找到了插入点
heroNode2.next = temp.next ;
heroNode2.pre = temp;
temp.next.pre = heroNode2 ;
temp.next = heroNode2 ;
break ;
}else if(temp.no == heroNode2.no){
System.out.printf("插入节点的编号%d 已经存在 \n" , heroNode2.no);
}
temp = temp.next ;
}
}
③ 修改节点内容:
// 修改节点操作:和单向链表类似,区别仅仅在节点类型的不同
public void update(HeroNode2 heroNode2) {
// 判断是否空
if (head.next == null) {
System.out.println("链表为空,无法修改");
return;
}
// 找到要修改的节点,根据no编号
// 定义一个辅助变量
HeroNode2 temp = head.next;
boolean flag = false; // 表示是否找到该节点
while (true) {
if (temp == null) { // 找到末尾了
break;
}
if (temp.no == heroNode2.no) {// 编号对应上
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = heroNode2.name;
temp.nickName = heroNode2.nickName;
} else {
System.out.printf("没有找到编号为%d 的节点,不能修改\n", heroNode2.no);
}
}
注意:此处定义了一个flag变量,当flag为true时,说明找到了修改节点;反之没有找到。
Q:为什么需要一个flag变量?为什么“添加节点”操作不需要用该变量?
A: 程序的基本逻辑是:找到了要修改的节点——>执行“修改操作”。在此处,“修改操作”包含的动作是完全一致的。
故可以直接用以下代码来统一运行。
if (flag) {
temp.name = heroNode2.name;
temp.nickName = heroNode2.nickName;
}
else{
...
}
而“添加节点”中,“添加到尾部”和“添加到中部”的操作是有区分的,因此不能简单地用一个flag变量作为判断条件,来统一执行。
这启发我们以后遇到重复类的条件操作时,可以借助一个条件变量flag,统一为一个条件 if(flag) 来运行。
④ 删除节点:
双链表删除节点的“核心动作”均为:
temp.pre.next = temp.next ;
if(temp.next != null){ // 在这里如果没有加if判断语句,会产生空指针异常
temp.next.pre = temp.pre; //这启示我们要有“空指针意识”
}
(temp指向当前需要删除的节点,进行自我删除。)
因此,同样可以设置一个布尔变量flag,用if(flag)统一为“找到所需删除的节点”,将该“核心动作”表示出来。
if(flag){
temp.pre.next = temp.next ;
if(temp.next != null){ // 在这里如果没有加if判断语句,会产生空指针异常
temp.next.pre = temp.pre; //这启示我们要有“空指针意识”
}
}
else{
......
}
注意:temp.next.pre = temp.pre; 能够执行的前提是“temp.next.pre”不为空,所以必须要加if( temp.next != null )的判断语句。
具体代码如下:
// 删除节点操作
public void del2(int no) {
if (head.next == null) {
System.out.println("链表为空,不能删除。");
return;
}
HeroNode2 temp = head.next; // 改动点1
boolean flag = false;
while (true) {
if (temp == null) { // 改动点2
break;
}
if (temp.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
/*
* 补充:单链表的删除方式 temp.next = temp.next.next ;
*/
// 双向链表的“自我删除”,改动点3
temp.pre.next = temp.next;
if (temp.next != null) { // 不加这个判断,会产生空指针异常
temp.next.pre = temp.pre;// 这句话在最后一个节点不成立
}
} else {
System.out.printf("没有找到编号为%d 的节点,不能删除\n ", no);
}
}