本文讲解的是线索化二叉树:因为二叉树像叶子节点呀,都是是有左右子节点是空闲状态,实现线索化后可以提高查询效率
二叉树的系列文章:
- 什么是二叉树(增删改查):https://blog.csdn.net/weixin_46635575/article/details/121121492
- 二叉树的顺序存储:https://blog.csdn.net/weixin_46635575/article/details/121188680
- 线索化二叉树:https://blog.csdn.net/weixin_46635575/article/details/121189266
- 树之赫夫曼树:https://blog.csdn.net/weixin_46635575/article/details/121269506
- 赫夫曼编码:https://blog.csdn.net/weixin_46635575/article/details/121295353
- 二叉排序树:https://blog.csdn.net/weixin_46635575/article/details/121443963
- 多叉树和B树:https://blog.csdn.net/weixin_46635575/article/details/121477970
一、线索化介绍
1、先看个问题
2、基本概念
- 第一句话的意思就是比如:把它的空指针,指向接下来的该遍历的数的前驱或后继。
- 不懂继续往后看
二、线索化的思路和实现
1、实现思路
1、案例分析
2、分析情况:来看看这个下面这些图片,
- 比如说第一个数8,前驱节点没有,后继节点为3 ;比如3,但是3的前驱和后继都是用完了的不能动;又比如10的前驱要指到3,后继1
3、问题说明
2、完整版代码实现
阅读建议
- 首先去节点类
- 然后再来看BinaryTree 类
package cn.mldn.Thread;
public class ThreadedBinaryTree {
public static void main(String[] args) {
//测试一把,和之前一样,先手动给关系
BinaryTree tree = new BinaryTree();
HeroNode root = new HeroNode(1,"A");
HeroNode heroNode2 = new HeroNode(3,"A");
HeroNode heroNode3 = new HeroNode(6,"A");
HeroNode heroNode4 = new HeroNode(8,"A");
HeroNode heroNode5 = new HeroNode(10,"A");
HeroNode heroNode6 = new HeroNode(14,"A");
root.setLeft(heroNode2);
root.setRight(heroNode3);
heroNode2.setLeft(heroNode4);
heroNode2.setRight(heroNode5 );
heroNode3.setRight(heroNode6);
//线索化一把
tree.setRoot(root);
tree.threadNodes(root);//这里同样可以设置重载
//测试,10号节点测试
System.out.println(heroNode5.getLeft());
}
}
class BinaryTree {
//-----------------------------------------------------------------
//下面都是之前编写的代码,直到+++++++++++++的结束
//-----------------------------------------------------------------
//首先,这个树里面最重要的是根节点
private HeroNode root;
public void setRoot(HeroNode heroNode) {
this.root = heroNode;
}
//前序遍历
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 root.postOrderSearch(no);
} else {
return null;
}
}
//删除节点
public void delNode(int no) {
//首先要判断是否为空,然后才能进行后面的操作,不然不行
if (root != null) {
//要判断root是否为要删除的节点,一定要先做
if (root.getNo() == no) {
root = null;//立刻就删除
} else {
//否则直接调用链表类的方法删除
root.deleteNode(no);
}
} else {
System.out.println("树为空,不能删除");
}
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//下面的才是这次编写的
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
private HeroNode pre = null;//这个变量时表示遍历当前节点的前一个节点辅助来进行线索化,递归进行遍历时表示前一个节点
//编写二叉树进行中序线索化的方法
public void threadNodes(HeroNode heroNode) {
//如果是一个空的
if (heroNode == null) {//说明为空
return;
}
//1、线索化第一步:左子树处理
threadNodes(heroNode.getLeft());
//2、线索化当前节点
//1)先处理当前节点的前驱节点
if (heroNode.getLeft() == null) {
//如果为null的情况下,就让它指向前驱节点
heroNode.setLeft(pre);
heroNode.setLeftType(1);
}
//2)然后处理后继节点,其实是在下一次的递归的时候进行处理的,有点不太好理解
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(heroNode);
pre.setLeftType(1);
}
//3)这句话特别重要哦
pre = heroNode;//每处理一个节点后,让当前节点是下一个节点当前前驱节点。
//3、线索化右子树
threadNodes(heroNode.getRight());
}
}
//创建节点类
//先创建节点
class HeroNode {
private int no;
private String name;
private HeroNode left;//左边节点,默认为null
private HeroNode right;//右边节点,默认为null
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//下面的两个属性是这次编写的,其他的是之前编写的
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//如果leftType = 0,表示的是指向左子树,如果leftType = 1则表示指向前驱节点
private int leftType;
//如果rightType = 0,表示的是指向右子树,如果rightType = 1则表示指向后继节点
private int rightType;
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'';
}
public void setNo(int no) {
this.no = no;
}
public void setName(String name) {
this.name = name;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public void setRight(HeroNode right) {
this.right = right;
}
public int getNo() {
return no;
}
public HeroNode getLeft() {
return left;
}
public HeroNode getRight() {
return right;
}
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 void preOrder() {
System.out.println(this);//先输出父节点
if (this.left != null) {//向左子树递归
this.left.preOrder();
}
//递归向右子子树前序遍历
//为什么不是else if呢,而是if呢,因为全部树都要遍历,肯定不能是要么遍历左边,要么遍历右边的
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 查找的on
* @return 找到返回,找不到返回null
*/
public HeroNode preOrderSearch(int no) {
//1、比较当前节点是不是
if (this.no == no) {
return this;
}
//判断当前节点是否为空,就像前序遍历一样的查找
HeroNode current = null;//零时变量用来保存找到的值
if (this.left != null) {
current = this.left.preOrderSearch(no);
}
//2、向左边查找
if (current != null) {//说明从前序遍历的左子树上找到了
return current;
}
//如果左边没有找到,则向右边递归
if (this.right != null) {
current = this.right.preOrderSearch(no);
}
//3、向右边查找
return current;
}
/**
* 中序遍历的查找
* @param no
* @return
*/
public HeroNode infixOrderSearch(int no) {
//1、判断当前节点的左子节点是否为空
HeroNode current = null;
if (this.left != null) {
current = this.left.infixOrderSearch(no);
}
if (current != null) {//说明中序遍历时,向前遍历的时候找到了
return current;
}
//2、如果没有找到则判断当前节点是否相等
if (this.no == no) {
return this;
}
//3、如果当前节点和向前遍历都没有扎到,则到像后面找
if (this.right != null) {
current = this.right.infixOrderSearch(no);
}
return current;
}
public HeroNode postOrderSearch(int no) {
//1、先判断左递归查找
HeroNode current = null;
if (this.left != null) {
current = this.left.postOrderSearch(no);
}
if (current != null) {//说明在左子树找到
return current;
}
//2、向右子树查找
if (this.right != null) {
current = this.right.postOrderSearch(no);
}
if (current != null) {
return current;
}
//3、如果左右都没有找到,判断当前节点是否等于
if (this.no == no) {
return this;
} else {
return current;
}
}
/**
* 首先就是规定
*
* 1、如果删除的节点是叶子节点,则删除该节点
* 2、如果删除的节点是子树,则删除该树
*/
public void deleteNode(int no) {
//1、先判断是否为左子节点,并且严谨一点要判断它是否为空
if (this.left != null && this.left.no == no) {
this.left = null;
return;
}
//2、如果当前节点的右子树不为空,并且右子节点,就是要删除节点,就将this.right = null,并且返回(结束递归)
if (this.right != null && this.right.no == no) {
this.right = null;
return;
}
//3、向左子树递归查找删除,1和2都没有找到的的话,则要向左子树递归删除,
if (this.left != null) {
this.left.deleteNode(no);
//暂时不要return,否则可能有问题
}
//4、向由子树递归查找删除,则应该想右子树进行递归删除
if (this.right != null) {
this.right.deleteNode(no);
}
}
}
效果看看
3、为了方便看版的代码
package cn.mldn.Thread;
public class ThreadedBinaryTree {
public static void main(String[] args) {
//测试一把,和之前一样,先手动给关系
BinaryTree tree = new BinaryTree();
HeroNode root = new HeroNode(1,"A");
HeroNode heroNode2 = new HeroNode(3,"A");
HeroNode heroNode3 = new HeroNode(6,"A");
HeroNode heroNode4 = new HeroNode(8,"A");
HeroNode heroNode5 = new HeroNode(10,"A");
HeroNode heroNode6 = new HeroNode(14,"A");
root.setLeft(heroNode2);
root.setRight(heroNode3);
heroNode2.setLeft(heroNode4);
heroNode2.setRight(heroNode5 );
heroNode3.setRight(heroNode6);
//线索化一把
tree.setRoot(root);
tree.threadNodes(root);//这里同样可以设置重载
//测试,10号节点测试
System.out.println(heroNode5.getLeft());
}
}
class BinaryTree {
//-----------------------------------------------------------------
//下面都是之前编写的代码,直到+++++++++++++的结束
//-----------------------------------------------------------------
//首先,这个树里面最重要的是根节点
private HeroNode root;
public void setRoot(HeroNode heroNode) {
this.root = heroNode;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//下面的才是这次编写的
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
private HeroNode pre = null;//这个变量时表示遍历当前节点的前一个节点辅助来进行线索化,递归进行遍历时表示前一个节点
//编写二叉树进行中序线索化的方法
public void threadNodes(HeroNode heroNode) {
//如果是一个空的
if (heroNode == null) {//说明为空
return;
}
//1、线索化第一步:左子树处理
threadNodes(heroNode.getLeft());
//2、线索化当前节点
//1)先处理当前节点的前驱节点
if (heroNode.getLeft() == null) {
//如果为null的情况下,就让它指向前驱节点
heroNode.setLeft(pre);
heroNode.setLeftType(1);
}
//2)然后处理后继节点,其实是在下一次的递归的时候进行处理的,有点不太好理解
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(heroNode);
pre.setLeftType(1);
}
//3)这句话特别重要哦
pre = heroNode;//每处理一个节点后,让当前节点是下一个节点当前前驱节点。
//3、线索化右子树
threadNodes(heroNode.getRight());
}
}
//创建节点类
//先创建节点
class HeroNode {
private int no;
private String name;
private HeroNode left;//左边节点,默认为null
private HeroNode right;//右边节点,默认为null
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//下面的两个属性是这次编写的,其他的是之前编写的
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//如果leftType = 0,表示的是指向左子树,如果leftType = 1则表示指向前驱节点
private int leftType;
//如果rightType = 0,表示的是指向右子树,如果rightType = 1则表示指向后继节点
private int rightType;
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'';
}
public void setNo(int no) {
this.no = no;
}
public void setName(String name) {
this.name = name;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public void setRight(HeroNode right) {
this.right = right;
}
public int getNo() {
return no;
}
public HeroNode getLeft() {
return left;
}
public HeroNode getRight() {
return right;
}
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;
}
}
三、遍历线索化二叉树
当线索化二叉树后,不能再使用原来的遍历方法了。为什么呢?
康康之前的遍历方法使用一下,出现了死循环,导致栈溢出
原理:线索化后,各个节点指向有变化,因此原理的遍历方式不能使用,这时需要使用新的方式遍历线索化,各个节点可以通过线型方式遍历,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率,遍历的次序当和对应的中序遍历保持一致。
1、问题引入
2、代码实现
其他的不变,再到main方法里面去调用测试一下,然后就可以了。
class BinaryTree {
//-----------------------------------------------------------------
//下面都是之前编写的代码,直到+++++++++++++的结束
//-----------------------------------------------------------------
//首先,这个树里面最重要的是根节点
private HeroNode root;
public void setRoot(HeroNode heroNode) {
this.root = heroNode;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//下面的才是这次编写的
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
private HeroNode pre = null;//这个变量时表示遍历当前节点的前一个节点辅助来进行线索化,递归进行遍历时表示前一个节点
//编写二叉树进行中序线索化的方法
public void threadNodes(HeroNode heroNode) {
//如果是一个空的
if (heroNode == null) {//说明为空
return;
}
//1、线索化第一步:左子树处理
threadNodes(heroNode.getLeft());
//2、线索化当前节点
//1)先处理当前节点的前驱节点
if (heroNode.getLeft() == null) {
//如果为null的情况下,就让它指向前驱节点
heroNode.setLeft(pre);
heroNode.setLeftType(1);
}
//2)然后处理后继节点,其实是在下一次的递归的时候进行处理的,有点不太好理解
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(heroNode);
pre.setLeftType(1);
}
//3)这句话特别重要哦
pre = heroNode;//每处理一个节点后,让当前节点是下一个节点当前前驱节点。
//3、线索化右子树
threadNodes(heroNode.getRight());
}
//线索化遍历二叉树的方法
public void threadList() {
//定义一个变量,存储当前的遍历的节点,从root开始
HeroNode node = root;
while (node != null) {
//1、循环的找到leftType == 1的节点,第一个找到就是8节点
//后面随着遍历而变化,因为当leftType == 1时,说明该节点是按照线索化处理后的有效节点。
while (node.getLeftType() == 0) {
node = node.getLeft();//一直找,直到LeftType = 1为止
}
//循环结束,说明找到了,输出即可
System.out.println(node);
//2、如果当前节点的右指针向的是后继节点,就一直输出
while (node.getRightType() == 1) {
//获取当前节点的后继节点
node = node.getRight();
System.out.println(node);
}
//3、当遇见不==1的时候,我们就替换这个为当前元素,然后继续往后面找
node = node.getRight();
}
}
}