线索化二叉树
先看一个问题
将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n+1=7
问题分析:
- 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }
-
但是 6, 8, 10, 14 这几个节点的 左右指针,并没有完全的利用上.
-
如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?
-
解决方案-线索二叉树
线索二叉树基本介绍
-
n 个结点的二叉链表中含有n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为”线索”)
-
这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
-
一个结点的前一个结点,称为前驱结点
- 一个结点的后一个结点,称为后继结点
线索二叉树应用案例
应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}
思路分析 :中序遍历的结果:{8, 3, 10, 1, 14, 6}
-
说明: 当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:
-
left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.
-
right 指向的是右子树,也可能是指向后继节点,比如 ① 节点 right 指向的是右子树,而⑩ 节点的 right 指向的是后继节点.
-
代码实现:
1 package com.xuge.tree.Threade;
2
3 /**
4 * author: yjx
5 * Date :2022/6/114:39
6 **/
7 public class ThreadeBinaryTreeDemo {
8 public static void main(String[] args) {
9 //测试中序线索二叉树
10 //1.创建一个二叉树
11 BinaryTree tree = new BinaryTree();
12 HeroNode root = new HeroNode(1, "送检费");
13 HeroNode node2 = new HeroNode(2, "宋江");
14 HeroNode node3 = new HeroNode(3, "轮训机");
15 HeroNode node4 = new HeroNode(4, "及食物");
16 HeroNode node5 = new HeroNode(5, "关胜");
17 HeroNode node6 = new HeroNode(6, "护眼做");
18 //二叉树
19 root.setLeft(node2);
20 root.setRight(node3);
21 node2.setLeft(node4);
22 node2.setRight(node5);
23 node3.setLeft(node6);
24 tree.setRoot(root);
25 // 4 2 5 1 6 3
26 //测试
27 tree.threadeNode();
28
29 HeroNode left = node5.getLeft();
30 System.out.println("node5前驱节点:"+ left);
31 System.out.println("node5后继节点:"+ node5.getRight());
32
33
34
35 }
36 }
37
38 //定义二叉树
39 class BinaryTree {
40 private HeroNode root;
41 //为了实现线索化,需要指向当前节点引用
42 //pre总是保留前一个前驱节点指针
43 private HeroNode pre;
44
45 public void threadeNode() {
46 this.threadeNode(root);
47 }
48 //编写对二叉树中序线索化
49
50 /**
51 * @param node 当前需要线索化的节点
52 */
53 public void threadeNode(HeroNode node) {
54 if (node == null) {
55 System.out.println("线索化节点为空..");
56 return;
57 }
58
59 //1.先线索化左子树
60 threadeNode(node.getLeft());
61 //2.线索化当前节点
62 //处理当前节点的前驱节点
63 if (node.getLeft() == null) {
64 //让当前节点左子树指向前驱节点
65 node.setLeft(pre);
66 //修改左子针类型
67 node.setLeftTyoe(1);
68 }
69 //处理后继节点
70 if (pre != null && pre.getRight() == null) {
71 //让前驱节点右子针指向当前节点
72 pre.setRight(node);
73 //修改前驱节点右子针类型
74 pre.setRightType(1);
75 }
76 //每处理一个节点,让当前节点是下一个节点的前驱节点
77 pre = node;
78 //3.先线索化右子树
79 threadeNode(node.getRight());
80 }
81
82 public void setRoot(HeroNode root) {
83 this.root = root;
84 }
85
86 //前序遍历
87 public void preOrder() {
88 if (this.root != null) {
89 this.root.preOrder();
90 } else {
91 System.out.println("二叉树为空,无法遍历");
92 }
93 }
94
95 //中序遍历
96 public void infixOrder() {
97 if (this.root != null) {
98 this.root.infixOrder();
99 } else {
100 System.out.println("二叉树为空,无法遍历");
101 }
102 }
103
104 //后序遍历
105 public void postOrder() {
106 if (this.root != null) {
107 this.root.postOrder();
108 } else {
109 System.out.println("二叉树为空,无法遍历");
110 }
111 }
112
113 public HeroNode proOrderSearch(int no) {
114 if (root != null) {
115 return root.preOrderSearch(no);
116 } else {
117 System.out.println("二叉树为空..");
118 return null;
119 }
120 }
121
122 public HeroNode infixOrderSearch(int no) {
123 if (root != null) {
124 return root.infixOrderSearch(no);
125 } else {
126 System.out.println("二叉树为空..");
127 return null;
128 }
129
130 }
131
132
133 public HeroNode postOrderSearch(int no) {
134 if (root != null) {
135 return root.postOrderSearch(no);
136 } else {
137 System.out.println("二叉树为空..");
138 return null;
139 }
140
141 }
142
143 public void deleteNode(int no) {
144 if (root != null) {
145 //判断root是否是要删除的节点
146 if (root.getNo() == no) {
147 root = null;
148 } else {
149 //递归删除
150 root.delete(no);
151 }
152 System.out.println("编号为" + no + "节点被删除..");
153 } else {
154 System.out.println("当前为空数,不可删除...");
155 }
156 }
157
158
159 }
160
161 //创建heroNode
162 class HeroNode {
163 private int no;
164 private String name;
165 private HeroNode left;
166 private HeroNode right;
167 private int leftTyoe;
168 private int rightType;
169
170 public int getLeftTyoe() {
171 return leftTyoe;
172 }
173
174 public void setLeftTyoe(int leftTyoe) {
175 this.leftTyoe = leftTyoe;
176 }
177
178 public int getRightType() {
179 return rightType;
180 }
181
182 public void setRightType(int rightType) {
183 this.rightType = rightType;
184 }
185
186 //说明:
187 //1.如果lefType=0;,表明指向的是左子树
188 //2.如果lefType=1,表明指向的是前驱节点
189 //3.如果rightType=0,表明指向的是右子树
190 //4.如果rightType=1,表明指向的是后继节点
191 public HeroNode(int no, String name) {
192 this.no = no;
193 this.name = name;
194 }
195
196 public void setNo(int no) {
197 this.no = no;
198 }
199
200 public void setName(String name) {
201 this.name = name;
202 }
203
204 public void setLeft(HeroNode left) {
205 this.left = left;
206 }
207
208 public void setRight(HeroNode right) {
209 this.right = right;
210 }
211
212 public int getNo() {
213 return no;
214 }
215
216 public String getName() {
217 return name;
218 }
219
220 public HeroNode getLeft() {
221 return left;
222 }
223
224 public HeroNode getRight() {
225 return right;
226 }
227
228 @Override
229 public String toString() {
230 return "HeroNode{" +
231 "no=" + no +
232 ", name='" + name + '\'' +
233 '}';
234 }
235
236 //编写前序遍历
237 public void preOrder() {
238 //1.先输出父节点
239 System.out.println(this);
240 if (this.left != null) {
241 this.left.preOrder();
242
243 }
244 //2.递归向右子树前序遍历
245 if (this.right != null) {
246 this.right.preOrder();
247 }
248 }
249
250
251 //编写中序遍历
252 public void infixOrder() {
253 //1.先递归左子树遍历
254 if (this.left != null) {
255 this.left.infixOrder();
256 }
257 //2.输出父节点
258 System.out.println(this);
259
260 //3.递归向右子树前序遍历
261 if (this.right != null) {
262 this.right.infixOrder();
263 }
264 }
265
266 //编写后序遍历
267
268 public void postOrder() {
269 //1.先递归左子树遍历
270 if (this.left != null) {
271 this.left.postOrder();
272
273 }
274 //2.递归向右子树前序遍历
275 if (this.right != null) {
276 this.right.postOrder();
277 }
278 //3.输出父节点
279 System.out.println(this);
280 }
281
282
283 /**
284 * 前序遍历查找
285 *
286 * @param no 查找编号
287 * @return 返回查找节点, 没有返回null
288 */
289 public HeroNode preOrderSearch(int no) {
290 System.out.println("=======进入前序遍历=======");
291 //判断当前节点是否是
292 if (this.no == no) {
293 return this;
294 }
295
296 //判断当前节点左子树是否为空,如果不为空,递归前序查找
297 //如果找到节点,则返回
298 HeroNode node = null;
299 if (this.left != null) {
300 node = this.left.preOrderSearch(no);
301 }
302 if (node != null) {//说明左子树找到了
303 return node;
304 }
305
306 //1.左递归前序查找,找到结点,则返回,否继续判断,
307 //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
308 if (this.right != null) {
309 node = this.right.preOrderSearch(no);
310 }
311 return node;
312 }
313
314
315 /**
316 * 中序遍历查找
317 *
318 * @param no 查找编号
319 * @return 返回查找节点, 没有返回null
320 */
321 public HeroNode infixOrderSearch(int no) {
322
323 //判断当前节点左子树是否为空,如果不为空,递归中序查找
324 //如果找到节点,则返回
325 HeroNode node = null;
326 if (this.left != null) {
327 node = this.left.infixOrderSearch(no);
328 }
329 if (node != null) {//说明左子树找到了
330 return node;
331 }
332 System.out.println("=======进入中序遍历=======");
333 //判断当前节点是否是
334 if (this.no == no) {
335 return this;
336 }
337
338 //1.右递归中序查找,找到结点,则返回,否继续判断,
339 //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归中序查找
340 if (this.right != null) {
341 node = this.right.infixOrderSearch(no);
342 }
343 return node;
344 }
345
346
347 /**
348 * 后序遍历查找
349 *
350 * @param no 查找编号
351 * @return 返回查找节点, 没有返回null
352 */
353 public HeroNode postOrderSearch(int no) {
354 //判断当前节点左子树是否为空,如果不为空,递归中序查找
355 //如果找到节点,则返回
356 HeroNode node = null;
357 if (this.left != null) {
358 node = this.left.postOrderSearch(no);
359 }
360 if (node != null) {//说明在左子树找到了
361 return node;
362 }
363 //如果左子树没有找到,去当前节点右递归
364 //1.右递归后序查找,找到结点,则返回,否继续判断,
365 //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归后序查找
366 if (this.right != null) {
367 node = this.right.postOrderSearch(no);
368 }
369 //左右子树节点都找不到
370 //判断当前节点是否是
371 System.out.println("=======进入后序遍历=======");
372
373 if (this.no == no) {
374 return this;
375 }
376 return node;
377 }
378
379 //HeroNode 类增加方法
380
381
382 //递归删除结点
383 //1.如果删除的节点是叶子节点,则删除该节点
384 //2.如果删除的节点是非叶子节点,则删除该子树
385
386 //思路
387 /*
388 * 1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
389
390 2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回
391 (结束递归删除)
392 3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回(结束递归删除)
393 4. 如果第 2 和第 3 步没有删除结点,那么我们就需要向左子树进行递归删除
394 5. 如果第 4 步也没有删除结点,则应当向右子树进行递归删除.
395
396
397 */
398 //2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回(结束递归删除)
399 public void delete(int no) {
400 //如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将 this.left = null; 并且就返回
401 if (this.left != null && this.left.no == no) {
402 this.left = null;
403 return;
404 }
405 //如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将 this.right= null ;并且就返回(结束递归删除)
406 if (this.right != null && this.right.no == no) {
407 this.right = null;
408 return;
409 }
410
411 //如果第 2 和第 3 步没有删除结点,那么我们就需要向左子树进行递归删除
412 if (this.left != null) {
413 this.left.delete(no);
414 }
415 //如果第 4 步也没有删除结点,则应当向右子树进行递归删除.
416 if (this.right != null) {
417 this.right.delete(no);
418 }
419 }
420
421
422 }
遍历线索化二叉树
- 说明:对前面的中序线索化的二叉树, 进行遍历
- 分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次序应当和中序遍历保持一致。
- 代码:
1 //遍历线索化二叉树的方法
2 public void threadList(){
3 //定义一个变量,存储当前遍历的节点,从root开始
4 HeroNode node=root;
5 while(node!=null){
6 //循环找到leftType=1的节点,第一个找到就是node4节点
7 //因为当leftType=1,说明该节点是按线索化处理后的有效节点
8 while(node.getLeftTyoe()==0){
9 node=node.getLeft();
10 }
11 //打印当前节点
12 System.out.println(node);
13 //如果当前节点的右指针是指向后继节点,就一直输出
14 while(node.getRightType()==1){
15 //获取到当前节点的后继节点
16 node=node.getRight();
17 System.out.println(node);
18 }
19 //替换遍历的节点
20 node=node.getRight();
21 }
22
23 }
Original: https://www.cnblogs.com/XugeA/p/16332734.html
Author: xugeA
Title: 树-线索化二叉树
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/605119/
转载文章受原作者版权保护。转载请注明原作者出处!