数据结构 树 二叉树

二叉树

为什么需要树这种数据结构

1) 数组存储方式的分析

优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低。

2) 链式存储方式的分析

优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,删除效率也很好)。
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)。

3) 树存储方式的分析

能提高数据存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。

树的常用术语

  1. 节点
  2. 根节点
  3. 父节点
  4. 子节点
  5. 叶子节点 (没有子节点的节点)
  6. 节点的权(节点值)
  7. 路径(从 root 节点找到该节点的路线)
  8. 子树
  9. 树的高度(最大层数)
  10. 森林 :多颗子树构成森林

    二叉树的概念

    树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
    二叉树的子节点分为左节点和右节点
    image.png
    如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 ,n 为层数,则称为满二叉树。
    image.png
    如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,称为完全二叉树。
    image.png

    二叉树遍历的说明

    使用前序,中序和后序对下面的二叉树进行遍历。
  • 前序遍历:先输出父节点,再遍历左子树和右子树
  • 中序遍历:先遍历左子树,再输出父节点,再遍历右子树
  • 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点

小结:看输出父节点的顺序,就确定是前序,中序还是后序。

二叉树遍历应用实例(前序,中序,后序)

思路分析

分析二叉树的前序,中序,后序的遍历步骤

  1. 创建一颗二叉树
  2. 前序遍历

2.1先输出当前节点(初始的时候是root节点)
2.2如果左子节点不为空,则递归继续前序遍历
2.3如果右子节点不为空,则递归继续前序遍历

  1. 中序遍历

3.1如果当前节点的左子节点不为空,则递归中序遍历,
3.2输出当前节点
3.3如果当前节点的右子节点不为空,则递归中序遍历

  1. 后序遍历

4.1如果当前节点的左子节点不为空,则递归后序遍历,
4.2如果当前节点的右子节点不为空,则递归后序遍历
4.3输出当前节点

代码实现

  1. public class BinaryTreeDemo {
  2. public static void main(String[] args) {
  3. //先需要创建一颗二叉树
  4. BinaryTree binaryTree = new BinaryTree();
  5. //创建需要的结点
  6. HeroNode root = new HeroNode(1, "宋江");
  7. HeroNode node2 = new HeroNode(2, "吴用");
  8. HeroNode node3 = new HeroNode(3, "卢俊义");
  9. HeroNode node4 = new HeroNode(4, "林冲");
  10. HeroNode node5 = new HeroNode(5, "关胜");
  11. //说明,我们先手动创建该二叉树,后面我们学习递归的方式创建二叉树
  12. root.setLeft(node2);
  13. root.setRight(node3);
  14. node3.setRight(node4);
  15. node3.setLeft(node5);
  16. binaryTree.setRoot(root);
  17. //测试
  18. // System.out.println("前序遍历"); // 1,2,3,5,4
  19. // binaryTree.preOrder();
  20. //测试
  21. // System.out.println("中序遍历");
  22. // binaryTree.infixOrder(); // 2,1,5,3,4
  23. //
  24. // System.out.println("后序遍历");
  25. // binaryTree.postOrder(); // 2,5,4,3,1
  26. }
  27. //定义BinaryTree 二叉树
  28. class BinaryTree {
  29. private HeroNode root;
  30. public void setRoot(HeroNode root) {
  31. this.root = root;
  32. }
  33. //删除结点
  34. public void delNode(int no) {
  35. if(root != null) {
  36. //如果只有一个root结点, 这里立即判断root是不是就是要删除结点
  37. if(root.getNo() == no) {
  38. root = null;
  39. } else {
  40. //递归删除
  41. root.delNode(no);
  42. }
  43. }else{
  44. System.out.println("空树,不能删除~");
  45. }
  46. }
  47. //前序遍历
  48. public void preOrder() {
  49. if(this.root != null) {
  50. this.root.preOrder();
  51. }else {
  52. System.out.println("二叉树为空,无法遍历");
  53. }
  54. }
  55. //中序遍历
  56. public void infixOrder() {
  57. if(this.root != null) {
  58. this.root.infixOrder();
  59. }else {
  60. System.out.println("二叉树为空,无法遍历");
  61. }
  62. }
  63. //后序遍历
  64. public void postOrder() {
  65. if(this.root != null) {
  66. this.root.postOrder();
  67. }else {
  68. System.out.println("二叉树为空,无法遍历");
  69. }
  70. }
  71. //前序遍历
  72. public HeroNode preOrderSearch(int no) {
  73. if(root != null) {
  74. return root.preOrderSearch(no);
  75. } else {
  76. return null;
  77. }
  78. }
  79. //中序遍历
  80. public HeroNode infixOrderSearch(int no) {
  81. if(root != null) {
  82. return root.infixOrderSearch(no);
  83. }else {
  84. return null;
  85. }
  86. }
  87. //后序遍历
  88. public HeroNode postOrderSearch(int no) {
  89. if(root != null) {
  90. return this.root.postOrderSearch(no);
  91. }else {
  92. return null;
  93. }
  94. }
  95. }
  96. //先创建HeroNode 结点
  97. class HeroNode {
  98. private int no;
  99. private String name;
  100. private HeroNode left; //默认null
  101. private HeroNode right; //默认null
  102. public HeroNode(int no, String name) {
  103. this.no = no;
  104. this.name = name;
  105. }
  106. public int getNo() {
  107. return no;
  108. }
  109. public void setNo(int no) {
  110. this.no = no;
  111. }
  112. public String getName() {
  113. return name;
  114. }
  115. public void setName(String name) {
  116. this.name = name;
  117. }
  118. public HeroNode getLeft() {
  119. return left;
  120. }
  121. public void setLeft(HeroNode left) {
  122. this.left = left;
  123. }
  124. public HeroNode getRight() {
  125. return right;
  126. }
  127. public void setRight(HeroNode right) {
  128. this.right = right;
  129. }
  130. @Override
  131. public String toString() {
  132. return "HeroNode [no=" + no + ", name=" + name + "]";
  133. }
  134. //编写前序遍历的方法
  135. public void preOrder() {
  136. System.out.println(this); //先输出父结点
  137. //递归向左子树前序遍历
  138. if(this.left != null) {
  139. this.left.preOrder();
  140. }
  141. //递归向右子树前序遍历
  142. if(this.right != null) {
  143. this.right.preOrder();
  144. }
  145. }
  146. //中序遍历
  147. public void infixOrder() {
  148. //递归向左子树中序遍历
  149. if(this.left != null) {
  150. this.left.infixOrder();
  151. }
  152. //输出父结点
  153. System.out.println(this);
  154. //递归向右子树中序遍历
  155. if(this.right != null) {
  156. this.right.infixOrder();
  157. }
  158. }
  159. //后序遍历
  160. public void postOrder() {
  161. if(this.left != null) {
  162. this.left.postOrder();
  163. }
  164. if(this.right != null) {
  165. this.right.postOrder();
  166. }
  167. System.out.println(this);
  168. }
  169. }

二叉树-查找指定节点

要求

  • 请编写前序查找,中序查找和后序查找的方法
  • 并分别使用三种查找方式,查找 heroNO = 5 的节点
  • 并分析各种查找方式,分别比较了多少次

    思路分析

    前序查找思路
  1. 先判断当前结点的no是否等于要查找的
  2. 如果是相等,则返回当前结点
  3. 如果不等,则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
  4. 如果左递归前序查找,找到结点,则返回,否则继续判断,当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找。

    中序查找思路
  5. 判断当前结点的左子节点是否为空,如果不为空,则递归中序查找

  6. 如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点,否则继续进行右递归的中序查找
  7. 如果右递归中序查找,找到就返回,否则返回null

    后序查找思路
  8. 判断当前结点的左子节点是否为空,如果不为空,则递归后序查找

  9. 如果找到,就返回,如果没有找到,就判断当前结点的右子节点是否为空,如果不为空,则右递归进行后序查找,如果找到,就返回
  10. 就和当前结点进行,比如,如果是则返回,否则返回null

    代码实现

    ```java public class BinaryTreeDemo {

    public static void main(String[] args) {

    1. //先需要创建一颗二叉树
    2. BinaryTree binaryTree = new BinaryTree();
    3. //创建需要的结点
    4. HeroNode root = new HeroNode(1, "宋江");
    5. HeroNode node2 = new HeroNode(2, "吴用");
    6. HeroNode node3 = new HeroNode(3, "卢俊义");
    7. HeroNode node4 = new HeroNode(4, "林冲");
    8. HeroNode node5 = new HeroNode(5, "关胜");
    9. //说明,我们先手动创建该二叉树,后面我们学习递归的方式创建二叉树
    10. root.setLeft(node2);
    11. root.setRight(node3);
    12. node3.setRight(node4);
    13. node3.setLeft(node5);
    14. binaryTree.setRoot(root);
    15. //测试

    // System.out.println(“前序遍历”); // 1,2,3,5,4 // binaryTree.preOrder();

     //测试 
    

    // System.out.println(“中序遍历”); // binaryTree.infixOrder(); // 2,1,5,3,4 //
    // System.out.println(“后序遍历”); // binaryTree.postOrder(); // 2,5,4,3,1

     //前序遍历
     //前序遍历的次数 :4 
    

    // System.out.println(“前序遍历方式~~~”); // HeroNode resNode = binaryTree.preOrderSearch(5); // if (resNode != null) { // System.out.printf(“找到了,信息为 no=%d name=%s”, resNode.getNo(), resNode.getName()); // } else { // System.out.printf(“没有找到 no = %d 的英雄”, 5); // }

     //中序遍历查找
     //中序遍历3次
    

    // System.out.println(“中序遍历方式~~~”); // HeroNode resNode = binaryTree.infixOrderSearch(5); // if (resNode != null) { // System.out.printf(“找到了,信息为 no=%d name=%s”, resNode.getNo(), resNode.getName()); // } else { // System.out.printf(“没有找到 no = %d 的英雄”, 5); // }

     //后序遍历查找
     //后序遍历查找的次数  2次
    

    // System.out.println(“后序遍历方式~~~”); // HeroNode resNode = binaryTree.postOrderSearch(5); // if (resNode != null) { // System.out.printf(“找到了,信息为 no=%d name=%s”, resNode.getNo(), resNode.getName()); // } else { // System.out.printf(“没有找到 no = %d 的英雄”, 5); // }

    }

}

//定义BinaryTree 二叉树 class BinaryTree { private HeroNode root;

public void setRoot(HeroNode root) {
    this.root = root;
}

//前序遍历
public void preOrder() {
    if(this.root != null) {
        this.root.preOrder();
    }else {
        System.out.println("二叉树为空,无法遍历");
    }
}

//中序遍历
public void infixOrder() {
    if(this.root != null) {
        this.root.infixOrder();
    }else {
        System.out.println("二叉树为空,无法遍历");
    }
}
//后序遍历
public void postOrder() {
    if(this.root != null) {
        this.root.postOrder();
    }else {
        System.out.println("二叉树为空,无法遍历");
    }
}

//前序遍历
public HeroNode preOrderSearch(int no) {
    if(root != null) {
        return root.preOrderSearch(no);
    } else {
        return null;
    }
}
//中序遍历
public HeroNode infixOrderSearch(int no) {
    if(root != null) {
        return root.infixOrderSearch(no);
    }else {
        return null;
    }
}
//后序遍历
public HeroNode postOrderSearch(int no) {
    if(root != null) {
        return this.root.postOrderSearch(no);
    }else {
        return null;
    }
}

}

//先创建HeroNode 结点 class HeroNode { private int no; private String name; private HeroNode left; //默认null private HeroNode right; //默认null public HeroNode(int no, String name) { this.no = no; this.name = name; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } public HeroNode getLeft() { return left; } public void setLeft(HeroNode left) { this.left = left; } public HeroNode getRight() { return right; } public void setRight(HeroNode right) { this.right = right; } @Override public String toString() { return “HeroNode [no=” + no + “, name=” + name + “]”; }

//编写前序遍历的方法
public void preOrder() {
    System.out.println(this); //先输出父结点
    //递归向左子树前序遍历
    if(this.left != null) {
        this.left.preOrder();
    }
    //递归向右子树前序遍历
    if(this.right != null) {
        this.right.preOrder();
    }
}
//中序遍历
public void infixOrder() {

    //递归向左子树中序遍历
    if(this.left != null) {
        this.left.infixOrder();
    }
    //输出父结点
    System.out.println(this);
    //递归向右子树中序遍历
    if(this.right != null) {
        this.right.infixOrder();
    }
}
//后序遍历
public void postOrder() {
    if(this.left != null) {
        this.left.postOrder();
    }
    if(this.right != null) {
        this.right.postOrder();
    }
    System.out.println(this);
}

//前序遍历查找
/**
 * 
 * @param no 查找no
 * @return 如果找到就返回该Node ,如果没有找到返回 null
 */
public HeroNode preOrderSearch(int no) {
    System.out.println("进入前序遍历");
    //比较当前结点是不是
    if(this.no == no) {
        return this;
    }
    //1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
    //2.如果左递归前序查找,找到结点,则返回
    HeroNode resNode = null;
    if(this.left != null) {
        resNode = this.left.preOrderSearch(no);
    }
    if(resNode != null) {//说明我们左子树找到
        return resNode;
    }
    //1.左递归前序查找,找到结点,则返回,否继续判断,
    //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
    if(this.right != null) {
        resNode = this.right.preOrderSearch(no);
    }
    return resNode;
}

//中序遍历查找
public HeroNode infixOrderSearch(int no) {
    //判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
    HeroNode resNode = null;
    if(this.left != null) {
        resNode = this.left.infixOrderSearch(no);
    }
    if(resNode != null) {
        return resNode;
    }
    System.out.println("进入中序查找");
    //如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
    if(this.no == no) {
        return this;
    }
    //否则继续进行右递归的中序查找
    if(this.right != null) {
        resNode = this.right.infixOrderSearch(no);
    }
    return resNode;

}

//后序遍历查找
public HeroNode postOrderSearch(int no) {

    //判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
    HeroNode resNode = null;
    if(this.left != null) {
        resNode = this.left.postOrderSearch(no);
    }
    if(resNode != null) {//说明在左子树找到
        return resNode;
    }

    //如果左子树没有找到,则向右子树递归进行后序遍历查找
    if(this.right != null) {
        resNode = this.right.postOrderSearch(no);
    }
    if(resNode != null) {
        return resNode;
    }
    System.out.println("进入后序查找");
    //如果左右子树都没有找到,就比较当前结点是不是
    if(this.no == no) {
        return this;
    }
    return resNode;
}

}

<a name="AxqQi"></a>
### 二叉树-删除节点
要求

- 如果删除的节点是叶子节点,则删除该节点
- 如果删除的节点是非叶子节点,则删除该子树
- 测试,删除掉 5 号叶子节点 和 3 号子树
<a name="v5GPn"></a>
#### 删除思路分析
完成删除结点的操作<br />规定:<br />如果删除的节点是叶子节点,则删除该节点<br />如果删除的节点是非叶子节点则删除该子树<br />思路<br />首先先处理:<br />考虑如果树是空树root,如果只有一个root结点,则等价将二叉树置空<br />//然后进行下面步骤

1. 因为二叉树是单向的,所以是判断当前结点的子结点是转需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
2. 如果当前结点的左子结点不为空,并且左子结点就是要删除结点,就将`this.left=null;`并且就返回(结束递归删除)
2. 如果当前结点的右子结点不为空,并且右子结点就是要删除结点,就将`this.right=null;`并且就返回(结束递归删除)
2. 如果第2和第3步没有删除结点,那么就需要向左子树进行递归删除
2. 如果第4步也没有删除结点,则应当向右子树进行递归删除。
<a name="EX0xk"></a>
#### 代码实现
```java
//HeroNode 类增加方法

//递归删除结点
//1.如果删除的节点是叶子节点,则删除该节点
//2.如果删除的节点是非叶子节点,则删除该子树
public void delNode(int no) {

    //思路
    /*
    *     1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
    2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
    3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
    4. 如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
    5.  如果第4步也没有删除结点,则应当向右子树进行递归删除.

    */
    //2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
    if(this.left != null && this.left.no == no) {
        this.left = null;
        return;
    }
    //3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
    if(this.right != null && this.right.no == no) {
        this.right = null;
        return;
    }
    //4.我们就需要向左子树进行递归删除
    if(this.left != null) {
        this.left.delNode(no);
    }
    //5.则应当向右子树进行递归删除
    if(this.right != null) {
        this.right.delNode(no);
    }
}

//在 BinaryTree 类增加方法

//删除结点
public void delNode(int no) {
    if(root != null) {
        //如果只有一个root结点, 这里立即判断root是不是就是要删除结点
        if(root.getNo() == no) {
            root = null;
        } else {
            //递归删除
            root.delNode(no);
        }
    }else{
        System.out.println("空树,不能删除~");
    }
}

//在 BinaryTreeDemo 类增加测试代码:

System.out.println("删除前,前序遍历");
binaryTree.preOrder(); //  1,2,3,5,4
binaryTree.delNode(5);
//binaryTree.delNode(3);
System.out.println("删除后,前序遍历");
binaryTree.preOrder(); // 1,2,3,4

顺序存储二叉树

顺序存储二叉树的概念

从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。
顺序存储二叉树的特点:

  1. 顺序二叉树通常只考虑完全二叉树
  2. 第 n 个元素的左子节点为 2 * n + 1
  3. 第 n 个元素的右子节点为 2 * n + 2
  4. 第 n 个元素的父节点为 (n-1) / 2
  5. n : 表示二叉树中的第几个元素(按 0 开始编号)

    顺序存储二叉树遍历

    需求:给一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。前序遍历的结果应当为1,2,4,5,3,6,7

    代码实现

    ```java public class ArrBinaryTreeDemo {

    public static void main(String[] args) {

     int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
     //创建一个 ArrBinaryTree
     ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
     arrBinaryTree.preOrder(); // 1,2,4,5,3,6,7
    

    }

}

//编写一个ArrayBinaryTree, 实现顺序存储二叉树遍历

class ArrBinaryTree { private int[] arr;//存储数据结点的数组

public ArrBinaryTree(int[] arr) {
    this.arr = arr;
}

//重载preOrder
public void preOrder() {
    this.preOrder(0);
}

//编写一个方法,完成顺序存储二叉树的前序遍历
/**
 * 
 * @param index 数组的下标 
 */
public void preOrder(int index) {
    //如果数组为空,或者 arr.length = 0
    if(arr == null || arr.length == 0) {
        System.out.println("数组为空,不能按照二叉树的前序遍历");
    }
    //输出当前这个元素
    System.out.println(arr[index]); 
    //向左递归遍历
    if((index * 2 + 1) < arr.length) {
        preOrder(2 * index + 1 );
    }
    //向右递归遍历
    if((index * 2 + 2) < arr.length) {
        preOrder(2 * index + 2);
    }
}

}

<a name="S325Z"></a>
## 线索化二叉树
<a name="lwBqM"></a>
### 先看一个问题
将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树,n+1=7<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/396745/1649513059717-3f694f85-2e09-4a0f-9958-f1268dfa7296.png#clientId=u793cd873-49a3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=209&id=u531c9763&margin=%5Bobject%20Object%5D&name=image.png&originHeight=279&originWidth=551&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6482&status=done&style=shadow&taskId=u13300f2e-7c20-4b8f-b1b5-39e1540d455&title=&width=413)<br />问题分析:

1. 当对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }
1. 但是 6, 8, 10, 14 这几个节点的左右指针,并没有完全的利用上
1. 如果希望充分的利用各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办? 
1. 解决方案-线索二叉树
<a name="p20Ik"></a>
### 线索二叉树基本介绍

1. n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
1. 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
1. 一个结点的前一个结点,称为前驱结点
1. 一个结点的后一个结点,称为后继结点  
<a name="nYqBP"></a>
### 线索二叉树应用案例
应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}<br />思路分析: 中序遍历的结果:{8, 3, 10, 1, 14, 6}<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/396745/1649513239468-24bb2c2f-e4c5-4a6b-a345-01073953dcad.png#clientId=u793cd873-49a3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=212&id=u60171232&margin=%5Bobject%20Object%5D&name=image.png&originHeight=283&originWidth=1344&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39666&status=done&style=shadow&taskId=u25540bad-8086-4dda-a97e-df4d8722226&title=&width=1008)<br />说明:当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:

1. left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树,而⑩节点的left 指向的就是前驱节点
1. right 指向的是右子树,也可能是指向后继节点,比如 ① 节点 right 指向的是右子树,而⑩节点的right 指向的是后继节点 
<a name="pGCrL"></a>
#### 代码实现
```java
import java.util.concurrent.SynchronousQueue;

public class ThreadedBinaryTreeDemo {

    public static void main(String[] args) {
        //测试一把中序线索二叉树的功能
        HeroNode root = new HeroNode(1, "tom");
        HeroNode node2 = new HeroNode(3, "jack");
        HeroNode node3 = new HeroNode(6, "smith");
        HeroNode node4 = new HeroNode(8, "mary");
        HeroNode node5 = new HeroNode(10, "king");
        HeroNode node6 = new HeroNode(14, "dim");

        //二叉树,后面我们要递归创建, 现在简单处理使用手动创建
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        //测试中序线索化
        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRoot(root);
        threadedBinaryTree.threadedNodes();

        //测试: 以10号节点测试
        HeroNode leftNode = node5.getLeft();
        HeroNode rightNode = node5.getRight();
        System.out.println("10号结点的前驱结点是 ="  + leftNode); //3
        System.out.println("10号结点的后继结点是="  + rightNode); //1

        //当线索化二叉树后,能在使用原来的遍历方法
        //threadedBinaryTree.infixOrder();
        System.out.println("使用线索化的方式遍历 线索化二叉树");
        threadedBinaryTree.threadedList(); // 8, 3, 10, 1, 14, 6

    }

}




//定义ThreadedBinaryTree 实现了线索化功能的二叉树
class ThreadedBinaryTree {
    private HeroNode root;

    //为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
    //在递归进行线索化时,pre 总是保留前一个结点
    private HeroNode pre = null;

    public void setRoot(HeroNode root) {
        this.root = root;
    }

    //重载一把threadedNodes方法
    public void threadedNodes() {
        this.threadedNodes(root);
    }

    //编写对二叉树进行中序线索化的方法
    /**
     * 
     * @param node 就是当前需要线索化的结点
     */
    public void threadedNodes(HeroNode node) {

        //如果node==null, 不能线索化
        if(node == null) {
            return;
        }

        //(一)先线索化左子树
        threadedNodes(node.getLeft());
        //(二)线索化当前结点[有难度]

        //处理当前结点的前驱结点
        //以8结点来理解
        //8结点的.left = null , 8结点的.leftType = 1
        if(node.getLeft() == null) {
            //让当前结点的左指针指向前驱结点 
            node.setLeft(pre); 
            //修改当前结点的左指针的类型,指向前驱结点
            node.setLeftType(1);
        }

        //处理后继结点
        if (pre != null && pre.getRight() == null) {
            //让前驱结点的右指针指向当前结点
            pre.setRight(node);
            //修改前驱结点的右指针类型
            pre.setRightType(1);
        }
        //!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
        pre = node;

        //(三)在线索化右子树
        threadedNodes(node.getRight());


    }

    //删除结点
    public void delNode(int no) {
        if(root != null) {
            //如果只有一个root结点, 这里立即判断root是不是就是要删除结点
            if(root.getNo() == no) {
                root = null;
            } else {
                //递归删除
                root.delNode(no);
            }
        }else{
            System.out.println("空树,不能删除~");
        }
    }
    //前序遍历
    public void preOrder() {
        if(this.root != null) {
            this.root.preOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }

    //中序遍历
    public void infixOrder() {
        if(this.root != null) {
            this.root.infixOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }
    //后序遍历
    public void postOrder() {
        if(this.root != null) {
            this.root.postOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }

    //前序遍历
    public HeroNode preOrderSearch(int no) {
        if(root != null) {
            return root.preOrderSearch(no);
        } else {
            return null;
        }
    }
    //中序遍历
    public HeroNode infixOrderSearch(int no) {
        if(root != null) {
            return root.infixOrderSearch(no);
        }else {
            return null;
        }
    }
    //后序遍历
    public HeroNode postOrderSearch(int no) {
        if(root != null) {
            return this.root.postOrderSearch(no);
        }else {
            return null;
        }
    }
}

//先创建HeroNode 结点
class HeroNode {
    private int no;
    private String name;
    private HeroNode left; //默认null
    private HeroNode right; //默认null
    //说明
    //1. 如果leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点
    //2. 如果rightType == 0 表示指向是右子树, 如果 1表示指向后继结点
    private int leftType;
    private int rightType;



    public int getLeftType() {
        return leftType;
    }
    public void setLeftType(int leftType) {
        this.leftType = leftType;
    }
    public int getRightType() {
        return rightType;
    }
    public void setRightType(int rightType) {
        this.rightType = rightType;
    }
    public HeroNode(int no, String name) {
        this.no = no;
        this.name = name;
    }
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public HeroNode getLeft() {
        return left;
    }
    public void setLeft(HeroNode left) {
        this.left = left;
    }
    public HeroNode getRight() {
        return right;
    }
    public void setRight(HeroNode right) {
        this.right = right;
    }
    @Override
    public String toString() {
        return "HeroNode [no=" + no + ", name=" + name + "]";
    }

    //递归删除结点
    //1.如果删除的节点是叶子节点,则删除该节点
    //2.如果删除的节点是非叶子节点,则删除该子树
    public void delNode(int no) {

        //思路
        /*
         *     1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
            2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
            3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
            4. 如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
            5.  如果第4步也没有删除结点,则应当向右子树进行递归删除.

         */
        //2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
        if(this.left != null && this.left.no == no) {
            this.left = null;
            return;
        }
        //3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
        if(this.right != null && this.right.no == no) {
            this.right = null;
            return;
        }
        //4.我们就需要向左子树进行递归删除
        if(this.left != null) {
            this.left.delNode(no);
        }
        //5.则应当向右子树进行递归删除
        if(this.right != null) {
            this.right.delNode(no);
        }
    }

    //编写前序遍历的方法
    public void preOrder() {
        System.out.println(this); //先输出父结点
        //递归向左子树前序遍历
        if(this.left != null) {
            this.left.preOrder();
        }
        //递归向右子树前序遍历
        if(this.right != null) {
            this.right.preOrder();
        }
    }
    //中序遍历
    public void infixOrder() {

        //递归向左子树中序遍历
        if(this.left != null) {
            this.left.infixOrder();
        }
        //输出父结点
        System.out.println(this);
        //递归向右子树中序遍历
        if(this.right != null) {
            this.right.infixOrder();
        }
    }
    //后序遍历
    public void postOrder() {
        if(this.left != null) {
            this.left.postOrder();
        }
        if(this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this);
    }

    //前序遍历查找
    /**
     * 
     * @param no 查找no
     * @return 如果找到就返回该Node ,如果没有找到返回 null
     */
    public HeroNode preOrderSearch(int no) {
        System.out.println("进入前序遍历");
        //比较当前结点是不是
        if(this.no == no) {
            return this;
        }
        //1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
        //2.如果左递归前序查找,找到结点,则返回
        HeroNode resNode = null;
        if(this.left != null) {
            resNode = this.left.preOrderSearch(no);
        }
        if(resNode != null) {//说明我们左子树找到
            return resNode;
        }
        //1.左递归前序查找,找到结点,则返回,否继续判断,
        //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
        if(this.right != null) {
            resNode = this.right.preOrderSearch(no);
        }
        return resNode;
    }

    //中序遍历查找
    public HeroNode infixOrderSearch(int no) {
        //判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
        HeroNode resNode = null;
        if(this.left != null) {
            resNode = this.left.infixOrderSearch(no);
        }
        if(resNode != null) {
            return resNode;
        }
        System.out.println("进入中序查找");
        //如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
        if(this.no == no) {
            return this;
        }
        //否则继续进行右递归的中序查找
        if(this.right != null) {
            resNode = this.right.infixOrderSearch(no);
        }
        return resNode;

    }

    //后序遍历查找
    public HeroNode postOrderSearch(int no) {

        //判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
        HeroNode resNode = null;
        if(this.left != null) {
            resNode = this.left.postOrderSearch(no);
        }
        if(resNode != null) {//说明在左子树找到
            return resNode;
        }

        //如果左子树没有找到,则向右子树递归进行后序遍历查找
        if(this.right != null) {
            resNode = this.right.postOrderSearch(no);
        }
        if(resNode != null) {
            return resNode;
        }
        System.out.println("进入后序查找");
        //如果左右子树都没有找到,就比较当前结点是不是
        if(this.no == no) {
            return this;
        }
        return resNode;
    }

}

遍历线索化二叉树

  1. 说明:对前面的中序线索化的二叉树, 进行遍历
  2. 分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。遍历的次序应当和中序遍历保持一致。
  3. 代码: ```java //ThreadedBinaryTree 类

//遍历线索化二叉树的方法 public void threadedList() { //定义一个变量,存储当前遍历的结点,从root开始 HeroNode node = root; while(node != null) { //循环的找到leftType == 1的结点,第一个找到就是8结点 //后面随着遍历而变化,因为当leftType==1时,说明该结点是按照线索化 //处理后的有效结点 while(node.getLeftType() == 0) { node = node.getLeft(); }

    //打印当前这个结点
    System.out.println(node);
    //如果当前结点的右指针指向的是后继结点,就一直输出
    while(node.getRightType() == 1) {
        //获取到当前结点的后继结点
        node = node.getRight();
        System.out.println(node);
    }
    //替换这个遍历的结点
    node = node.getRight();

}

} ```