2.2.0 什么是树
-
树(Tree)是一种抽象数据类型,用来模拟实现具有树状类型结构的数据集合。它具有n(n>=0)个有层次的有限结点。当n=0时,称为空树;n>0时,其余结点分为m个互斥的有限集合T1,T2,T3,每个集合分别称为子树
-
树示意图
- 与栈、队列和链表不同,树是一种**非线性**数据结构。**一颗树只有一个根结点**。拥有多颗根结点的数据结构是树的集合,称之为森林。
树的常用术语:
-
结点的度
结点拥有子树的个数称为该结点的度。如上图中B的度为2,C的度为1。度为0的结点称之为叶子结点或终端结点,度不为0的结点称之为分支结点或非终端结点。除根结点外,分支结点也称之为内部结点
-
树的度
树的度是树内各个结点度的最大值
-
父结点、子女结点、兄弟结点、叶子结点
若结点x有子女结点,则它为其子女结点的父结点。
若结点x存在子树,则子树的根结点称之为结点x的子女结点
同一父结点的子女互称兄弟。如上图中B、C、D互称兄弟。
没有子结点的结点就是叶子结点
-
层次与深度
根结点所处的层次为第1层,其子女所属的层次为第2层。以此类推,若某结点所处的层次为第i层,其子女所处的层次为第i+1层。**树中结点的最大层次称之为树的深度或高度。**上图中树的深度为4。
-
路径
从一个结点到另外一个结点所经过的结点,比如A到E的路径是ABE
- 二叉树
- 连续: 从最一边结点开始连续,没有中断。举个例子:右上方的树,原本是右连续的71 61 15
但是如果将61结点删除掉,那么连续到71就断了 就不再连续了。
- 前序、中序、后序遍历
DLR–前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )
LDR–中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)
LRD–后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面
- 举个例子
2.2.1 数组、链表、树 存储方式分析
2.2.2 二叉树的前序中序后序遍历
思路:
代码实现:
1.前序遍历
/**
* 前中后序遍历的区别只是输出(访问中间节点)的顺序之差
*/
// 前序遍历
public void preOrderTraversal(){
System.out.println(this);
// 左递归
if (this.left != null)
this.left.preOrderTraversal();
// 右递归
if (this.right != null)
this.right.preOrderTraversal();
}
2.中序遍历
public void infixOrderTraversal(){
// 左递归
if (this.left != null)
this.left.infixOrderTraversal();
System.out.println(this);
// 右递归
if (this.right != null)
this.right.infixOrderTraversal();
}
3.后序遍历
public void postOrderTraversal(){
// 左递归
if (this.left != null)
this.left.postOrderTraversal();
// 右递归
if (this.right != null)
this.right.postOrderTraversal();
System.out.println(this);
}
2.2.3 二叉树的前中后序查找
需求:
思路:
代码实现:
// 前序查找
public heroNode preInfixSearch(int no){
// 本节点的no就是查找节点 那么直接返回
if(this.no == no){
return this;
}
heroNode result = null;
// 左节点不为空 进行左递归
if(this.left != null){
result = this.left.preInfixSearch(no);
}
if (result != null) // 当result不为空 那么就代表找到了节点
return result;
else { // 否则左递归没有找到节点,准备进行右递归
if (this.right != null)
result = this.right.preInfixSearch(no);
if (result != null)
return result;
}
return null;
}
// 中序查找
public heroNode infixSearch(int no){
heroNode result = null;
// 左节点不为空 进行左递归
if(this.left != null){
result = this.left.infixSearch(no);
}
if (result != null) // 当result不为空 那么就代表找到了节点
return result;
// 在左递归为空 若当前节点是查找节点 返回
// 本节点的no就是查找节点 那么直接返回
if(this.no == no){
return this;
}
// 前面查找都为空情况下 开始右递归查找
if (this.right != null){
result = this.right.infixSearch(no);
}
return result;
}
// 后序查找 前后中
public heroNode postSearch(int no){
heroNode result = null;
if (this.left != null)
result = this.left.postSearch(no);
if (result != null)
return result;
else{ // 左递归没有查找到 开始右递归
if (this.right != null)
result = this.right.postSearch(no);
if (result != null)
return result;
}
// 本节点的no就是查找节点 那么直接返回
if(this.no == no){
return this;
}
return null;
}
2.2.4 二叉树删除节点
规定:
-
为了简化删除操作,暂时规定如果删除节点为非叶子节点,那么直接删除以该节点为根节点的子树
-
如果删除节点为叶子节点,那么直接删除该节点
实现思路:
代码实现:
// 删除节点
public boolean deleteNode(int no){
// 如果左节点为删除节点
if (this.left!=null && this.left.no == no){
this.left = null;
return true;
}
if (this.right!=null && this.right.no==no){
this.right = null;
return true;
}
boolean isFind = false;
if (this.left != null){
isFind = this.left.deleteNode(no);
if (isFind)
return true;
}
if (this.right != null){
isFind = this.right.deleteNode(no);
return isFind;
}
return false;
}
2.2.5 完整代码及测试实例
class heroNode{
int no;
String name;
heroNode left; // 左子节点
heroNode right; // 右子节点
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 preOrderTraversal(){
System.out.println(this);
// 左递归
if (this.left != null)
this.left.preOrderTraversal();
// 右递归
if (this.right != null)
this.right.preOrderTraversal();
}
// 中序遍历
public void infixOrderTraversal(){
// 左递归
if (this.left != null)
this.left.infixOrderTraversal();
System.out.println(this);
// 右递归
if (this.right != null)
this.right.infixOrderTraversal();
}
// 后序遍历
public void postOrderTraversal(){
// 左递归
if (this.left != null)
this.left.postOrderTraversal();
// 右递归
if (this.right != null)
this.right.postOrderTraversal();
System.out.println(this);
}
// 前序查找
public heroNode preInfixSearch(int no){
// 本节点的no就是查找节点 那么直接返回
if(this.no == no){
return this;
}
heroNode result = null;
// 左节点不为空 进行左递归
if(this.left != null){
result = this.left.preInfixSearch(no);
}
if (result != null) // 当result不为空 那么就代表找到了节点
return result;
else { // 否则左递归没有找到节点,准备进行右递归
if (this.right != null)
result = this.right.preInfixSearch(no);
if (result != null)
return result;
}
return null;
}
// 中序查找
public heroNode infixSearch(int no){
heroNode result = null;
// 左节点不为空 进行左递归
if(this.left != null){
result = this.left.infixSearch(no);
}
if (result != null) // 当result不为空 那么就代表找到了节点
return result;
// 在左递归为空 若当前节点是查找节点 返回
// 本节点的no就是查找节点 那么直接返回
if(this.no == no){
return this;
}
// 前面查找都为空情况下 开始右递归查找
if (this.right != null){
result = this.right.infixSearch(no);
}
return result;
}
// 后序查找 前后中
public heroNode postSearch(int no){
heroNode result = null;
if (this.left != null)
result = this.left.postSearch(no);
if (result != null)
return result;
else{ // 左递归没有查找到 开始右递归
if (this.right != null)
result = this.right.postSearch(no);
if (result != null)
return result;
}
// 本节点的no就是查找节点 那么直接返回
if(this.no == no){
return this;
}
return null;
}
// 删除节点
public boolean deleteNode(int no){
// 如果左节点为删除节点
if (this.left!=null && this.left.no == no){
this.left = null;
return true;
}
if (this.right!=null && this.right.no==no){
this.right = null;
return true;
}
boolean isFind = false;
if (this.left != null){
isFind = this.left.deleteNode(no);
if (isFind)
return true;
}
if (this.right != null){
isFind = this.right.deleteNode(no);
return isFind;
}
return false;
}
}
class BinaryTree{
private heroNode root; // 根节点root
public heroNode getRoot() {
return root;
}
public void setRoot(heroNode root) {
this.root = root;
}
public void preOrder(){
if (root == null){
System.out.println("二叉树为空!");
return;
}
root.preOrderTraversal();
}
public void infixOrder(){
if (root == null){
System.out.println("二叉树为空!");
return;
}
root.infixOrderTraversal();
}
public void postOrder(){
if (root == null){
System.out.println("二叉树为空!");
return;
}
root.postOrderTraversal();
}
public heroNode preSearch(int no){
if (root == null){
System.out.println("树的根节点root没有初始化!");
return null;
}
if (root.preInfixSearch(no) != null)
return root.preInfixSearch(no);
else {
System.out.println("当前树结构内无该节点"+no);
return null;
}
}
public heroNode infixSearch(int no){
if (root == null){
System.out.println("树的根节点root没有初始化!");
return null;
}
if (root.infixSearch(no) != null)
return root.infixSearch(no);
else {
System.out.println("当前树结构内无该节点"+no);
return null;
}
}
public heroNode postSearch(int no){
if (root == null){
System.out.println("树的根节点root没有初始化!");
return null;
}
if (root.postSearch(no) != null)
return root.postSearch(no);
else {
System.out.println("当前树结构内无该节点"+no);
return null;
}
}
public void deleteNode(int no){
if (root==null){
System.out.println("root根节点未初始化!");
return;
}
if (root.no==no){
root = null;
}else if (root.deleteNode(no))
System.out.println("删除成功!");
else
System.out.println("没有找到节点,删除失败!");
}
}
public class BinaryTreeDemo {
public static void main(String[] args) {
// 因为还没有写递归创建二叉树结点的方法 所以为了测试暂时先手动创建二叉树的节点
BinaryTree binaryTree = new BinaryTree();
heroNode root = new heroNode(0,"root");
heroNode node1 = new heroNode(1,"t1");
heroNode node2 = new heroNode(2,"t2");
heroNode node3 = new heroNode(3,"t3");
heroNode node4 = new heroNode(4,"t4");
root.setLeft(node1);
root.setRight(node2);
node1.setLeft(node3);
node1.setRight(node4);
binaryTree.setRoot(root);
System.out.println("前序遍历: ");
binaryTree.preOrder();
System.out.println("中序遍历: ");
binaryTree.infixOrder();
System.out.println("后序遍历: ");
binaryTree.postOrder();
// 开始查找测试
System.out.println("前序查找结果: ");
heroNode heroNode = binaryTree.preSearch(2);
System.out.println(heroNode);
binaryTree.preSearch(20);
// 中序查找测试
System.out.println("中序查找结果: ");
System.out.println(binaryTree.infixSearch(3));
// 后序查找测试
System.out.println("后序查找结果: ");
System.out.println(binaryTree.postSearch(4));
binaryTree.deleteNode(4);
binaryTree.preOrder();
}
}
输出结果:
前序遍历:
heroNode: [No: 0, name: root]
heroNode: [No: 1, name: t1]
heroNode: [No: 3, name: t3]
heroNode: [No: 4, name: t4]
heroNode: [No: 2, name: t2]
中序遍历:
heroNode: [No: 3, name: t3]
heroNode: [No: 1, name: t1]
heroNode: [No: 4, name: t4]
heroNode: [No: 0, name: root]
heroNode: [No: 2, name: t2]
后序遍历:
heroNode: [No: 3, name: t3]
heroNode: [No: 4, name: t4]
heroNode: [No: 1, name: t1]
heroNode: [No: 2, name: t2]
heroNode: [No: 0, name: root]
前序查找结果:
heroNode: [No: 2, name: t2]
当前树结构内无该节点20
中序查找结果:
heroNode: [No: 3, name: t3]
后序查找结果:
heroNode: [No: 4, name: t4]
删除成功!
heroNode: [No: 0, name: root]
heroNode: [No: 1, name: t1]
heroNode: [No: 3, name: t3]
heroNode: [No: 2, name: t2]
2.2.6 顺序存储二叉树
介绍:
思路:
- 使用数组来存储树的节点
- 本实例仅为了讲解思路,所以只是使用int数组来表示树节点数组
- 顺序存储二叉树要求存储的二叉树为完全二叉树,根据完全二叉树的特点有了以下几条规则(定义根节点root下标为0)
- 每一个节点的左子节点的下标为2n+1(如果有的话)
- 每一个节点的右子节点的下标为2n+2(如果有的话)
- 每一个节点的父节点的下标为(n-1)/2(如果有的话)
实现代码:
// 实现顺序存储二叉树
class ArrayBinaryTree{
private int[] arr = null;
public ArrayBinaryTree(int[] arr){
this.arr = arr;
}
// 重载preOrder方法
public void preOrder(){
this.preOrder(0);
}
public void infixOrder(){
this.infixOrder(0);
}
public void postOrder(){
this.postOrder(0);
}
/**
* 前序遍历输出顺序二叉树
* @param index 从下标为index开始的子树开始遍历顺序二叉树
*/
public void preOrder(int index){
// 1. 需要先判断下arr数组是否为null或长度为0
if(arr == null || arr.length == 0){
System.out.println("当前数组为null或长度为0!");
return;
}
// 2. 输出当前节点
if (index < arr.length)
System.out.println(arr[index]);
else
return;
// 3. 开始左递归
if (2*index+1 < arr.length)
preOrder(2*index+1);
// 4. 开始右递归
if (2*index+2 < arr.length)
preOrder(2*index+2);
}
// 中序遍历
public void infixOrder(int index){
// 1. 需要先判断下arr数组是否为null或长度为0
if(arr == null || arr.length == 0){
System.out.println("当前数组为null或长度为0!");
return;
}
// 2. 开始左递归
if (2*index+1 < arr.length)
infixOrder(2*index+1);
// 3. 输出当前节点
if (index < arr.length)
System.out.println(arr[index]);
else
return;
// 4. 开始右递归
if (2*index+2 < arr.length)
infixOrder(2*index+2);
}
// 后序遍历
public void postOrder(int index){
// 1. 需要先判断下arr数组是否为null或长度为0
if(arr == null || arr.length == 0){
System.out.println("当前数组为null或长度为0!");
return;
}
// 2. 开始左递归
if (2*index+1 < arr.length)
postOrder(2*index+1);
// 3. 开始右递归
if (2*index+2 < arr.length)
postOrder(2*index+2);
// 4. 输出当前节点
if (index < arr.length)
System.out.println(arr[index]);
}
}
public class arrayBinaryTreeDemo {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5,6,7};
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
System.out.println("前序: ");
arrayBinaryTree.preOrder(0);
System.out.println("中序: ");
arrayBinaryTree.infixOrder();
System.out.println("后序: ");
arrayBinaryTree.postOrder();
}
}
输出结果:
前序:
1
2
4
5
3
6
7
中序:
4
2
5
1
6
3
7
后序:
4
5
2
6
7
3
1
2.2.7 线索化二叉树
问题引出:
介绍:
- 线索化二叉树的目的,将原二叉树结点的空指针域都利用起来,先遍历二叉树,判断节点的左右指针域是否为空,为空就将其利用起来(指向前驱/后继节点)
- 线索化二叉树的节点数据结构为 【pre,data,post】pre指向当前节点的前一个节点,称为前驱结点,post指向当前节点的后一个节点,称为后继节点
- 因为遍历次序的不同,线索化二叉树也分为前序线索二叉树,中序线索二叉树,后序线索二叉树
- 前驱后继的概念是对于遍历二叉树的顺序来讲的
思路:
- 线索化二叉树需要考虑的一个问题:可能出现指针指向节点身份问题,指向的是左右子节点还是自己的前驱/后继节点,所以需要改造节点属性,额外添加识别left和right指针指向节点类别的标识leftType和rightType(0: 左右子节点 1:前驱后继)
- 为了实现线索化,需要在二叉树类内添加一个属性指针pre指向当前节点的前驱结点
- 中序线索化二叉树
- 先线索化本节点的左子树
- 再线索化本节点
- 如果左指针为空,让其指向前驱节点,修改左指针类型(解决前驱节点指针问题)
- 如果当前节点的前驱节点的右指针为空 让其指向当前节点 修改前驱节点的指针类型(解决后继节点指针问题) (为什么要先遍历到下一个节点才能处理之前节点的后继节点的指针指向问题,因为在还没有遍历到下一个节点的时候,是无法获取到下一个节点的地址的,所以也无法将right指针指向下一个节点)
- 最后线索化本节点右子树
- 前序线索化二叉树和后序线索化二叉树与中序线索化二叉树的步骤差别不大,区别只是在于更新pre指针的时机,具体可看接下来的代码了解
实现代码:
// 中序线索化二叉树
public void infixThreadNodes(heroNode2 node){
// 1. 检测node是否为空
if (node == null){
System.out.println("初始节点未初始化!");
return;
}
// 2. 线索化左子树
if (node.left != null)
this.infixThreadNodes(node.left);
// 3. 线索化当前节点
// 3.1 如果左指针为空,让其指向前驱节点,修改左指针类型(解决前驱节点指针问题)
if (node.left == null && node.leftType == 0){
node.left = pre;
node.leftType = 1;
}
// 3.2 如果当前节点的前驱节点的右指针为空 让其指向当前节点 修改前驱节点的指针类型(解决后继节点指针问题)
if (pre != null && pre.right == null && pre.rightType == 0){
pre.right = node;
pre.rightType = 1;
}
// 3.3 在线索化当前节点后 需要修改pre指针指向当前节点
pre = node;
// 4.线索化右子树
if(node.right != null)
this.infixThreadNodes(node.right);
}
// 前序线索化二叉树
public void preThreadNodes(heroNode2 node){
if (node == null){
System.out.println("初始节点未初始化!");
return;
}
// 线索化当前节点
if (node.left == null && node.leftType == 0){
node.left = pre;
node.leftType = 1;
}
if (pre != null && pre.right == null && pre.rightType == 0){
pre.right = node;
pre.rightType = 1;
}
// 记得变化pre指针
pre = node;
// 递归线索化左子树
if (node.left != null && node.leftType == 0) {
this.preThreadNodes(node.left);
}
// 递归线索化右子树
if(node.right != null && node.rightType == 0)
this.preThreadNodes(node.right);
}
// 后序线索化二叉树
public void postThreadNodes(heroNode2 node){
if (node == null){
System.out.println("初始节点未初始化!");
return;
}
// 递归线索化左子树
if (node.left != null && node.leftType == 0) {
this.postThreadNodes(node.left);
}
// 递归线索化右子树
if(node.right != null && node.rightType == 0)
this.postThreadNodes(node.right);
// 线索化当前节点
if (node.left == null && node.leftType == 0){
node.left = pre;
node.leftType = 1;
}
if (pre != null && pre.right == null && pre.rightType == 0){
pre.right = node;
pre.rightType = 1;
}
// 记得变化pre指针
pre = node;
}
2.2.8 遍历线索化二叉树
中序遍历线索化二叉树:
原中序遍历结果:[8,3,10,1,14,6]
思路:
- 先寻找到树最左端的节点,从该节点开始遍历
- 判断该节点是否有后继节点,有则输出该后继节点,开始遍历,直到无后继节点停止
- 最后右移node节点
- 重复以上三步直到node == null遍历结束
代码:
/**
* 中序遍历线索化二叉树
*/
public void infixThreadedShow(){
heroNode2 node = root;
while (node!=null){ // 中序结束条件是遍历到为null的节点
// 查找最左端节点 从这里开始遍历
while (node.leftType == 0){
node.leftType = 1;
node = node.getLeft();
}
// 输出该节点
System.out.println(node);
// 判断当前节点是否有后继节点
while (node.rightType == 1){
node = node.right;
System.out.println(node);
}
// 最后移动node遍历节点
node = node.right;
}
}
前序遍历线索化二叉树:
思路:
- 从根节点root开始遍历
- 若有节点有后继节点,开始遍历,直到当前节点无后继节点
- 若节点左节点无前驱节点,节点开始左移
- 循环以上三步直到满足遍历结束条件
- 遍历结束条件为节点右节点为空且rightType == 0
代码:
/**
* 前序遍历线索二叉树
*/
public void preThreadedShow(){
heroNode2 node = root;
while (node!=null){
System.out.println(node);
while (node.rightType == 1){
node = node.right;
System.out.println(node);
}
if (node.leftType == 0) {
node.leftType = 1;
node = node.left;
}
// 因为线索化的缘故 线索化后的二叉树的最后一个节点的rightType == 0 && right == null
if (node.rightType == 0 && node.right == null) // 前序遍历结束条件是遍历到一个节点右指针域为空且rightType == null
break;
}
}
后序遍历线索化二叉树:
- 1.后序遍历需要改造herNode 要额外添加一个属性parent指针指向当前节点的父节点
- 2.先寻找到树最左端的节点,从该节点开始遍历
- 3.判断本节点时是否有后继节点,有则开始遍历,每次node节点移动都要将pre指针指向当前节点前一个节点
- 4.若当前节点为root节点代表遍历结束,跳出循环
- 5.当遍历到的节点出现,pre == node.right时候代表以本节点为根节点的子树已遍历完成,需要遍历本节点的兄弟节点为根节点的子树了,需要先获取本节点的父节点,再从父节点那获取到兄弟节点。(这里就用到了第一步改造节点多出来的属性parent了)
- 6.重复2-5步,直到遍历结束跳出循环
代码:
public void postThreadedShow(){
heroNode2 node = root;
while (node!=null){
while (node.leftType == 0){
node.leftType = 1;
node = node.getLeft();
}
// 判断当前节点是否有后继节点
while (node.rightType == 1){
System.out.println(node);
pre = node;
node = node.right;
}
// 若node节点为root节点 代表遍历结束
if (node == root){
System.out.println(node);
return;
}
// 若pre == node.getRight() 代表以node节点为根节点的子树已经遍历结束
// 需要获取node节点的兄弟节点(先获取父节点才能获取兄弟节点)
while (node!=null && node.right == pre){
System.out.println(node);
pre = node;
node = node.getParent();
}
if (node!=null && node.rightType == 0)
node = node.getRight(); // 获取兄弟节点
}
}
完整代码与测试实例:
// 线索二叉树实现
class heroNode2{
int no;
String name;
heroNode2 left; // 左子节点指针
heroNode2 right; // 右子节点指针
heroNode2 parent;
int leftType = 0; // 左指针类别 0:左子节点 1:前驱节点
int rightType = 0; // 右指针类别 0:右子节点 1:后继节点
public int getLeftType(){
return this.leftType;
}
public int getRightType(){
return this.rightType;
}
public void setLeftType(int leftType){
this.leftType = leftType;
}
public void setRightType(int rightType){
this.rightType = rightType;
}
public heroNode2 getParent(){
return parent;
}
public void setParent(heroNode2 parent){
this.parent = parent;
}
public heroNode2(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 heroNode2 getLeft() {
return left;
}
public void setLeft(heroNode2 left) {
this.left = left;
}
public heroNode2 getRight() {
return right;
}
public void setRight(heroNode2 right) {
this.right = right;
}
@Override
public String toString() {
return "heroNode2: [No: "+no+", name: "+name+"]";
}
}
class ThreadBinaryTree{
private heroNode2 pre = null; // 指向当前节点的前驱节点的指针
private heroNode2 root; // 二叉树的root根节点
public heroNode2 getPre() {
return pre;
}
public void setPre(heroNode2 pre) {
this.pre = pre;
}
public heroNode2 getRoot() {
return root;
}
public void setRoot(heroNode2 root) {
this.root = root;
}
// 中序线索化二叉树
public void infixThreadNodes(heroNode2 node){
// 1. 检测node是否为空
if (node == null){
System.out.println("初始节点未初始化!");
return;
}
// 2. 线索化左子树
if (node.left != null)
this.infixThreadNodes(node.left);
// 3. 线索化当前节点
// 3.1 如果左指针为空,让其指向前驱节点,修改左指针类型(解决前驱节点指针问题)
if (node.left == null && node.leftType == 0){
node.left = pre;
node.leftType = 1;
}
// 3.2 如果当前节点的前驱节点的右指针为空 让其指向当前节点 修改前驱节点的指针类型(解决后继节点指针问题)
if (pre != null && pre.right == null && pre.rightType == 0){
pre.right = node;
pre.rightType = 1;
}
// 3.3 在线索化当前节点后 需要修改pre指针指向当前节点
pre = node;
// 4.线索化右子树
if(node.right != null)
this.infixThreadNodes(node.right);
}
// 前序线索化二叉树
public void preThreadNodes(heroNode2 node){
if (node == null){
System.out.println("初始节点未初始化!");
return;
}
// 线索化当前节点
if (node.left == null && node.leftType == 0){
node.left = pre;
node.leftType = 1;
}
if (pre != null && pre.right == null && pre.rightType == 0){
pre.right = node;
pre.rightType = 1;
}
// 记得变化pre指针
pre = node;
// 递归线索化左子树
if (node.left != null && node.leftType == 0) {
this.preThreadNodes(node.left);
}
// 递归线索化右子树
if(node.right != null && node.rightType == 0)
this.preThreadNodes(node.right);
}
// 后序线索化二叉树
public void postThreadNodes(heroNode2 node){
if (node == null){
System.out.println("初始节点未初始化!");
return;
}
// 递归线索化左子树
if (node.left != null && node.leftType == 0) {
this.postThreadNodes(node.left);
}
// 递归线索化右子树
if(node.right != null && node.rightType == 0)
this.postThreadNodes(node.right);
// 线索化当前节点
if (node.left == null && node.leftType == 0){
node.left = pre;
node.leftType = 1;
}
if (pre != null && pre.right == null && pre.rightType == 0){
pre.right = node;
pre.rightType = 1;
}
// 记得变化pre指针
pre = node;
}
/**
* 中序遍历线索化二叉树
*/
public void infixThreadedShow(){
heroNode2 node = root;
while (node!=null){ // 中序结束条件是遍历到为null的节点
// 查找最左端节点 从这里开始遍历
while (node.leftType == 0){
node.leftType = 1;
node = node.getLeft();
}
// 输出该节点
System.out.println(node);
// 判断当前节点是否有后继节点
while (node.rightType == 1){
node = node.right;
System.out.println(node);
}
// 最后移动node遍历节点
node = node.right;
}
}
/**
* 前序遍历线索二叉树
*/
public void preThreadedShow(){
heroNode2 node = root;
while (node!=null){
System.out.println(node);
while (node.rightType == 1){
node = node.right;
System.out.println(node);
}
if (node.leftType == 0) {
node.leftType = 1;
node = node.left;
}
// 因为线索化的缘故 线索化后的二叉树的最后一个节点的rightType == 0 && right == null
if (node.rightType == 0 && node.right == null) // 前序遍历结束条件是遍历到一个节点右指针域为空且rightType == null
break;
}
}
// 后序遍历线索二叉树
// 后序遍历需要改造herNode 要额外添加一个属性parent指针指向当前节点的父节点
public void postThreadedShow(){
heroNode2 node = root;
while (node!=null){
while (node.leftType == 0){
node.leftType = 1;
node = node.getLeft();
}
// 判断当前节点是否有后继节点
while (node.rightType == 1){
System.out.println(node);
pre = node;
node = node.right;
}
// 若node节点为root节点 代表遍历结束
if (node == root){
System.out.println(node);
return;
}
// 若pre == node.getRight() 代表以node节点为根节点的子树已经遍历结束
// 需要获取node节点的兄弟节点(先获取父节点才能获取兄弟节点)
while (node!=null && node.right == pre){
System.out.println(node);
pre = node;
node = node.getParent();
}
if (node!=null && node.rightType == 0)
node = node.getRight(); // 获取兄弟节点
}
}
}
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
// 1.测试线索化二叉树
ThreadBinaryTree threadBinaryTree = new ThreadBinaryTree();
heroNode2 root = new heroNode2(0,"root");
heroNode2 node1 = new heroNode2(1,"t1");
heroNode2 node2 = new heroNode2(2,"t2");
heroNode2 node3 = new heroNode2(3,"t3");
heroNode2 node4 = new heroNode2(4,"t4");
heroNode2 node5 = new heroNode2(5,"t5");
heroNode2 node6 = new heroNode2(6,"t6");
heroNode2 node7 = new heroNode2(7,"t7");
root.setLeft(node1);
root.setRight(node2);
node1.setLeft(node3);
node1.setRight(node4);
node2.setLeft(node5);
node2.setRight(node6);
node3.setLeft(node7);
node1.setParent(root);
node2.setParent(root);
node3.setParent(node1);
node4.setParent(node1);
node5.setParent(node2);
node6.setParent(node2);
node7.setParent(node3);
threadBinaryTree.setRoot(root);
// 测试前中后序遍历需要分开测试,不能同时测试,否则会出现错误
threadBinaryTree.infixThreadNodes(root);
System.out.println("中序:");
threadBinaryTree.infixThreadedShow(); // 7 3 1 4 0 5 2 6
// threadBinaryTree.preThreadNodes(root);
// System.out.println("前序:");
// threadBinaryTree.preThreadedShow(); // 0 1 3 7 4 2 5 6
// threadBinaryTree.postThreadNodes(root);
// System.out.println("后序:");
// threadBinaryTree.postThreadedShow(); // 7 3 4 1 5 6 2 0
}
}
中序遍历输出结果:
中序:
heroNode2: [No: 7, name: t7]
heroNode2: [No: 3, name: t3]
heroNode2: [No: 1, name: t1]
heroNode2: [No: 4, name: t4]
heroNode2: [No: 0, name: root]
heroNode2: [No: 5, name: t5]
heroNode2: [No: 2, name: t2]
heroNode2: [No: 6, name: t6]
前序遍历输出结果:
前序:
heroNode2: [No: 0, name: root]
heroNode2: [No: 1, name: t1]
heroNode2: [No: 3, name: t3]
heroNode2: [No: 7, name: t7]
heroNode2: [No: 4, name: t4]
heroNode2: [No: 2, name: t2]
heroNode2: [No: 5, name: t5]
heroNode2: [No: 6, name: t6]
后序遍历输出结果:
后序:
heroNode2: [No: 7, name: t7]
heroNode2: [No: 3, name: t3]
heroNode2: [No: 4, name: t4]
heroNode2: [No: 1, name: t1]
heroNode2: [No: 5, name: t5]
heroNode2: [No: 6, name: t6]
heroNode2: [No: 2, name: t2]
heroNode2: [No: 0, name: root]