9.3-线索化二叉树
- 二叉树中,我们会发现一个问题,就是会存在空指针域,因为不是每个结点都有左右结点,所以这个时候会造成空间浪费,为了减少这种不必要的浪费,我们将这些空指针域,指向某一遍历次序下,当前结点的前驱和后续结点,那么我们就将一个二叉树转化成了线索化二叉树
- 二叉树有n个结点,则有n+1个空指针域
package com.hejiale.dataStructures.tree.threadBinaryTree;
public class ThreadBinaryTree {
private HeroNode root;//根结点
//为了实现线索化,需要创建一个指向当前结点前驱结点的一个指针
private HeroNode pre = null;
public ThreadBinaryTree(HeroNode root) {
this.root = root;
}
/*
- 中序线索化二叉树,其实归根到底,是对结点进行线索化
- node 就是当前需要线索化的结点
- 在递归进行线索化的时候,pre总是指向当前结点的前一个结点
- 我们每次递归,是解决当前结点的前驱结点pre,解决pre结点的后继结点node,而不是一步到位,一次性解决当前结点的前驱和后继
1. 如果node结点为空,则无法线索化该结点,退出
2. 先线索化左子树
3. 线索化当前结点
3.1 先处理当前结点的前驱结点
3.2 处理当前结点的后继结点
4. 线索化右子树
*/
public void infixThreadNodes(HeroNode node) {
if (node == null) {
return;
}
//线索化左子树
infixThreadNodes(node.getLeft());
//线索化当前结点
if (node.getLeft() == null) {
//让当前结点的左指针指向前驱结点
node.setLeft(pre);
//修改当前结点的左指针类型
node.setLeftType(1);//表明该指针指向的是前驱结点
}
//处理后继结点
if (pre != null && pre.getRight() == null) {
pre.setRight(node);
pre.setRightType(1);
}
//每处理完一个结点后,让当前结点是下一个结点
pre = node;
//线索化右子树
infixThreadNodes(node.getRight());
}
public void preThreadNodes(HeroNode node) {
if (node == null) {
return;
}
//线索化当前结点
if (node.getLeft() == null) {
//让当前结点的左指针指向前驱结点
node.setLeft(pre);
//修改当前结点的左指针类型
node.setLeftType(1);//表明该指针指向的是前驱结点
}
//处理后继结点
if (pre != null && pre.getRight() == null) {
pre.setRight(node);
pre.setRightType(1);
}
//每处理完一个结点后,让当前结点是下一个结点
pre = node;
//线索化左子树
if (node.getLeftType() != 1) {
preThreadNodes(node.getLeft());
}
//再线索化右子树
if (node.getRightType() != 1) {
preThreadNodes(node.getRight());
}
}
//后序线索二叉树
public void postThreadNodes(HeroNode node) {
if (node == null) {
return;
}
//线索化左子树
postThreadNodes(node.getLeft());
postThreadNodes(node.getRight());
//线索化当前结点
if (node.getLeft() == null) {
//让当前结点的左指针指向前驱结点
node.setLeft(pre);
//修改当前结点的左指针类型
node.setLeftType(1);//表明该指针指向的是前驱结点
}
//处理后继结点
if (pre != null && pre.getRight() == null) {
pre.setRight(node);
pre.setRightType(1);
}
//每处理完一个结点后,让当前结点是下一个结点
pre = node;
//线索化右子树
}
//遍历中序线索化二叉树
/*
首先,中序遍历二叉树,第一个结点应该是什么样子的呢?我们可以分类讨论
1. 只有左子树-->不成立,因为有左子树,那么一定会先遍历左子树的结点,所以有左子树的一定不是中序遍历的第一个结点
2. 只有右子树-->可以,右子树的所有结点,按照左中右的遍历顺序,一定不会出现再该节点的前面
3. 有左右子树-->只有左子树的情况不成立,那么该情况肯定也不成立
4. 没有子树-->成立
那么以上四种情况分析完后,我们可以得出,中序遍历的第一个结点,一定是没有左子树的,所以leftType一定是1,并且,由于是
中序遍历,按照左中右的次序,所以第一个结点,应该出现再树的最左下方,是纯粹的左方,所以我们可以while循环遍历,如果
leftType!=1,我们就令node = node.getLeft(),那么跳出循环时候的node,就是我们要找的中序遍历第一个结点
*/
public void threadInfixList() {
//定义一个变量,临时存储当前遍历的结点
HeroNode node = root;
while (node != null) {//不等于空,就可以遍历
//循环找到leftType = 1的结点,第一个找到的就是
while (node.getLeftType() == 0) {
node = node.getLeft();
}//跳出循环的node就是我们要找的中序遍历的第一个结点
//打印该结点
System.out.println(node);
//如果当前结点的右指针指向的是后继结点,就一直输出
while (node.getRightType() == 1) {
//获取到当前结点的后继jiedian
node = node.getRight();
System.out.println(node);
}//跳出循环,说明遇到了RightType != 1的结点,那么这个时候
/*
这个时候,不知道应该替换哪一个结点,我们同样可以分类讨论
首先,遇到了RightType == 0的结点,说明当前结点是有右子树的,当前结点已经遍历了,那么这个时候无非就
两种情况,要么遍历左子树结点,要么遍历右子树结点
但是我们是中序遍历,左子树的结点肯定已经遍历了,如果我们遍历左子树的结点就会违背中序遍历的顺序
所以我们应该遍历的是右子树结点
*/
node = node.getRight();
/*
右子树结点进行下一次while循环,同样遵循先访问最左下结点的规律
*/
}
}
/*
遍历先序线索二叉树
先序线索二叉树的遍历是最简单的,思路在代码中体现
*/
public void threadPreList() {
HeroNode node = root;
while (node != null) {
while (node.getLeftType() == 0) {//说明该结点没有前驱结点,则输出
System.out.println(node);
node = node.getLeft();
}//当结点没有左子树的时候
System.out.println(node);//输出该结点
/*
更新要遍历的结点,因为该结点没有左子树,那么接下来要遍历的结点分为两种情况
1. 该结点没有左子树,但是有右子树,则node = node.getRight()
2. 该结点是叶子结点,那么接下来应该访问该结点的后继结点,node = node.getRight()
所以不论是哪种情况,都是node = node.getRight()
*/
node = node.getRight();
}
}
public void threadPostList() {
HeroNode node = root;
while (node != null) {
//找到第一个叶子结点
while (node.getRightType() != 1 && node.getLeftType() != 1) {
if (node.getLeftType() == 0) {
node = node.getLeft();
} else {
node = node.getRight();
}
}//跳出循环的结点就是第一个叶子结点
System.out.println(node);//输出当前结点
while (node.getRightType() == 1) {//如果当前结点右指针指向的是后继结点,则继续输出
node = node.getRight();
System.out.println(node);
}
}
}
}