树-线索化二叉树

线索化二叉树

先看一个问题

将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n+1=7

树-线索化二叉树

问题分析:

  1. 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }
  2. 但是 6, 8, 10, 14 这几个节点的 左右指针,并没有完全的利用上.

  3. 如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?

  4. 解决方案-线索二叉树

线索二叉树基本介绍

  1. n 个结点的二叉链表中含有n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为”线索”)

  2. 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种

  3. 一个结点的前一个结点,称为前驱结点

  4. 一个结点的后一个结点,称为后继结点

线索二叉树应用案例

应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {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. 分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次序应当和中序遍历保持一致。
  3. 代码:
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/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球