树-线索化二叉树

线索化二叉树

先看一个问题

将数列 {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】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")

 

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

 

  1. 一个结点的前一个结点,称为前驱结点
  2. 一个结点的后一个结点,称为后继结点

 

 

线索二叉树应用案例

 

 

应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}

思路分析: 中序遍历的结果:{8, 3, 10, 1, 14, 6}

 

 

 

 

    • 说明: 当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:
  1. left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.
  2. 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   }
复制代码

 

 

posted @   xugeA  阅读(76)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示