树-线索化二叉树
线索化二叉树
先看一个问题
将数列 {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 }