一、双向链表介绍
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表
双向链表图示
双向链表与单链表对比
1、单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
2、单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除。所以前面我们单链表删除节点时,总是找到 temp, temp 是待删除节点的前一个节点。
二、java代码实现双向链表的增删改查
2.1 应用案例
使用双向链表实现 – 水浒英雄排行榜管理完成对英雄人物的增删改查操作。
2.2 编写HeroNode2类
1 public class HeroNode2 {
2 public int no;
3 public String name;
4 public String nickname;
5 public HeroNode2 next; // 指向下一个节点,默认为null
6 public HeroNode2 pre; // 指向上一个节点,默认为null
7
8 // 构造器
9 public HeroNode2(int no, String name, String nickname) {
10 this.no = no;
11 this.name = name;
12 this.nickname = nickname;
13 }
14
15 // 为了显示方法,重写toString方法
16 @Override
17 public String toString() {
18 return "HeroNode2{" +
19 "no=" + no +
20 ", name='" + name + '\'' +
21 ", nickname='" + nickname + '\'' +
22 '}';
23 }
24 }
2.3 编写DoubleLinkedList类
1 /**
2 * @description: 创建一个双向链表的类
3 * @author: hyr
4 * @time: 2020/1/19 9:48
5 */
6 public class DoubleLinkedList {
7 // 先初始化一个头节点,头节点不要动,不存放具体的数据
8 private HeroNode2 head = new HeroNode2(0, "", "");
9
10 // 返回头节点
11 public HeroNode2 getHead() {
12 return head;
13 }
14
15 // 显示链表
16 public void list() {
17 // 判断链表是否为空
18 if (head.next == null) {
19 System.out.println("链表为空");
20 return;
21 }
22 // 因为头节点,不能动,因此我们需要一个辅助变量来遍历
23 HeroNode2 temp = head.next;
24 while (true) {
25 // 判断是否到链表最后
26 if (temp == null) {
27 break;
28 }
29 // 输出节点的信息
30 System.out.println(temp);
31
32 // 将temp后移
33 temp = temp.next;
34 }
35 }
36
37 // 添加一个节点到双向链表的最后
38 public void add(HeroNode2 heroNode) {
39 // 因为头节点不能动,因此我们需要一个辅助变量temp
40 HeroNode2 temp = head;
41 // 遍历链表,找到最后
42 while (true) {
43 // 找到链表的最后
44 if (temp.next == null) {
45 break;
46 }
47 // 如果没有找到,将temp后移
48 temp = temp.next;
49 }
50 // 当退出循环时,temp就指向了链表的最后
51 // 形成一个双向链表
52 temp.next = heroNode;
53 heroNode.pre = temp;
54 }
55
56 // 按照顺序添加
57 public void add1(HeroNode2 heroNode) {
58 // 因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
59 // 因为单链表,因为我们找的 temp 是位于添加位置的前一个节点,否则插入不了
60 HeroNode2 temp = head;
61 // 标志添加的编号是否存在,默认为false
62 boolean flag = false;
63 while (true) {
64 // 说明 temp 已经在链表的最后
65 if (temp.next == null) {
66 break;
67 }
68 // 位置找到,就在 temp 的后面插入
69 if (temp.next.no > heroNode.no) {
70 break;
71 }
72 // 说明希望添加的 heroNode 的编号已然存在
73 else if (temp.next.no == heroNode.no) {
74 //说明编号存在
75 flag = true;
76 break;
77 }
78 // 后移,遍历当前链表
79 temp = temp.next;
80 }
81 // 判断 flag 的值
82 // 不能添加,说明编号存在
83 if (flag) {
84 System.out.printf("准备插入的英雄的编号%d已经存在了,不能加入\n", heroNode.no);
85 } else {
86 // 插入到链表中,temp的后面
87 // 这里需要判断一下是不是最后一个节点,需要不同的操作。
88 if (temp.next != null) {
89 heroNode.next = temp.next;
90 heroNode.next.pre = heroNode;
91 }
92 temp.next = heroNode;
93 heroNode.pre = temp;
94 }
95 }
96
97 // 修改一个节点的内容,可以看到双向链表的节点内容修改和单向链表一样
98 // 只是节点类型修改为HeroNode2
99 public void update(HeroNode2 newHeroNode) {
100 // 判断是否为空
101 if (head.next == null) {
102 System.out.println("链表为空");
103 return;
104 }
105 // 找到需要修改的节点,根据no编号
106 // 定义一个辅助变量
107 HeroNode2 temp = head.next;
108 boolean flag = false; // 表示是否找到该节点
109 while (true) {
110 if (temp == null) {
111 break; // 已经遍历完链表
112 }
113 if (temp.no == newHeroNode.no) {
114 // 找到
115 flag = true;
116 break;
117 }
118 temp = temp.next;
119 }
120 // 根据flag判断是否找到要修改的节点
121 if (flag) {
122 temp.name = newHeroNode.name;
123 temp.nickname = newHeroNode.nickname;
124 } else {
125 System.out.printf("没有找到编号为%d的节点,不能修改\n", newHeroNode.no);
126 }
127 }
128
129 // 从双向链表中删除一个节点
130 // 说明
131 // 1、对于双向链表,我们可以直接找到要删除的这个节点
132 // 2、找到后,自我删除即可
133 public void del(int no) {
134 // 判断当前链表是否为空
135 if (head.next == null) {
136 System.out.println("链表为空,无法删除");
137 return;
138 }
139 HeroNode2 temp = head.next; // 辅助变量
140 boolean flag = false; // 标志是否找到待删除节点
141 while (true) {
142 if (temp == null) {
143 // 已经到了链表的最后
144 break;
145 }
146 if (temp.no == no) {
147 // 找到的待删除节点的前一个节点temp
148 flag = true;
149 break;
150 }
151 temp = temp.next; // temp后移,遍历
152 }
153 // 判断flag
154 if (flag) { // 找到
155 // 可以删除
156 temp.pre.next = temp.next;
157 // 如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
158 if (temp.next != null) {
159 temp.next.pre = temp.pre;
160 }
161 } else {
162 System.out.printf("要删除的%d节点不存在\n", no);
163 }
164 }
165 }
2.4 编写测试类DoubleLinkedDemo
1 /**
2 * @description: 使用双向链表完成对水浒英雄榜的增删改查
3 * @author: hyr
4 * @time: 2020/1/19 18:25
5 */
6 public class DoubleLinkedDemo {
7 public static void main(String[] args) {
8 // 先创建节点
9 HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
10 HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
11 HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");
12 HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");
13
14 // 创建一个双向链表
15 DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
16
17 // // 1、按照no順序向双向链表中增添数据
18 // doubleLinkedList.add1(hero1);
19 // doubleLinkedList.add1(hero2);
20 // doubleLinkedList.add1(hero3);
21 // doubleLinkedList.add1(hero4);
22 // // 查看一下双向链表中的数据
23 // System.out.println("增添完数据后的双向链表为:");
24 // doubleLinkedList.list();
25 // System.out.println();
26
27 // 2、按照no順序向双向链表中增添数据
28 doubleLinkedList.add1(hero1);
29 doubleLinkedList.add1(hero3);
30 doubleLinkedList.add1(hero2);
31 doubleLinkedList.add1(hero4);
32 // 查看一下双向链表中的数据
33 System.out.println("增添完数据后的双向链表为:");
34 doubleLinkedList.list();
35 System.out.println();
36
37 // 3、修改
38 HeroNode2 newHeroNode = new HeroNode2(4,"鲁智深", "花和尚");
39 doubleLinkedList.update(newHeroNode);
40 System.out.println("修改后的双向链表为:");
41 doubleLinkedList.list();
42 System.out.println();
43
44 // 4、删除
45 doubleLinkedList.del(3);
46 System.out.println("删除后的链表情况为:");
47 doubleLinkedList.list();
48 }
49 }