双向链表

  • 与单向链表的对比

(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);
			}
		}