什么是线索二叉树
一个普通的二叉树,使用链式存储:
对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树
对于上面的二叉树,结点:8,10,14,6存在空指针,8,10,14左右结点指针都为null,6的右指针为null,如果把左指针连接上前驱结点,右指针连接上后继结点,这就是线索化
如果该二叉树结点都线索化,就是线索二叉树
前驱结点、后继结点在不同的遍历方式下会不同,所以线索二叉树又可以分为:前序线索二叉树、中序线索二叉树和后序线索二叉树三种
上面的二叉树线索化后的中序线索二叉树:绿色指针为前驱、后继指针
线索二叉树有什么用?
我们的普通的二叉树,存在无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题
使用线索二叉树,前驱指针、后继指针将遍历序列连接
但设置了前驱指针、后继指针,遍历方式又存在问题,需要定义新的遍历方式
如果对先序、中序、后序遍历不清楚,可以看数据结构 - 树、二叉树及四种遍历解析实现
中序线索二叉树思路
实现步骤:
- 设置一个辅助变量pre,表示前驱结点,初始化为null;根据中序遍历的方法,从根结点出发,递归左子树,直到找到第一个左结点为null的结点8
- 把结点8的leftType=1,设置左结点指向前驱结点pre,left=null
-
设置后继结点,当pre不为空时,设置pre的右指针指向当前结点,RightType=1 (因为当前pre为空,不执行,后续会执行,用于设置后继指针)
-
处理完一个结点,当前结点会是下一结点的前驱结点,赋值pre为当前结点
-
根据中序遍历,处理结点3,左结点不为空,不需要处理前驱结点;设置后继结点pre,
pre.setRight(node);pre.setRightType(1);
- 将结点3赋值给pre,进行循环上面的步骤
Java实现中序线索二叉树
使用链式存储的二叉树
结点类HeroNode:
- 设置左右结点指针
- 需要设置左右结点类型,如果type=0,表示左右结点指向的子树,如果type=1,表示指向的是前驱结点、后继结点
//创建HeroNode结点
class HeroNode{
private int no;
private String name;
//左右结点,默认为null
private HeroNode left;
private HeroNode right;
//如果leftType == 0,表示指向左子树,如果为1指向前驱结点
private int leftType;
//如果rightType == 0,表示指向右子树,如果为1指向后继结点
private int rightType;
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
public int getLeftType() {
return leftType;
}
public HeroNode setLeftType(int leftType) {
this.leftType = leftType;
return this;
}
public int getRightType() {
return rightType;
}
public HeroNode setRightType(int rightType) {
this.rightType = rightType;
return this;
}
public int getNo() {
return no;
}
public HeroNode setNo(int no) {
this.no = no;
return this;
}
public String getName() {
return name;
}
public HeroNode setName(String name) {
this.name = name;
return this;
}
public HeroNode getLeft() {
return left;
}
public HeroNode setLeft(HeroNode left) {
this.left = left;
return this;
}
public HeroNode getRight() {
return right;
}
public HeroNode setRight(HeroNode right) {
this.right = right;
return this;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
二叉树类ThreadedBinaryTree:
线索化类似与中序遍历,用递归的方法实现上面的思路
//ThreadedBinaryTree,线索化二叉树
class ThreadedBinaryTree{
//根结点
private HeroNode root;
//实现线索化需要一个指向当前结点的前驱结点的指针
private HeroNode pre = null;
public ThreadedBinaryTree(HeroNode root) {
this.root = root;
}
public void setRoot(HeroNode root) {
this.root = root;
}
//重载
public void threadedNodes() {
threadedNodes(root);
}
/**
* @param node 当前需要线索化的结点
* 编写对二叉树进行中序线索化
*/
public void threadedNodes(HeroNode node){
//如果node == null,不能线索化
if (node == null){
return;
}
//1.先线索化左子树
threadedNodes(node.getLeft());
//2.线索化当前结点
//处理当前结点的前驱结点,当为null时需要线索化
if (node.getLeft() == null){
//让当前结点的左指针指向前驱结点
node.setLeft(pre);
//修改当前结点的左指针类型
node.setLeftType(1);
}
//处理后继结点
if (pre != null && pre.getRight() == null){
//让前驱结点的右指针指向当前结点
pre.setRight(node);
pre.setRightType(1);
}
//每处理完一个结点,当前结点是下一个结点的前驱结点
pre = node;
//3.线索化右子树
threadedNodes(node.getRight());
}
}
测试类:
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
HeroNode root = new HeroNode(1,"宋江");
HeroNode heroNode2 = new HeroNode(3,"吴用");
HeroNode heroNode3 = new HeroNode(6,"卢俊义");
HeroNode heroNode4 = new HeroNode(8,"林冲");
HeroNode heroNode5 = new HeroNode(10,"鲁智深");
HeroNode heroNode6 = new HeroNode(14, "燕青");
//创建二叉树
root.setLeft(heroNode2);
root.setRight(heroNode3);
heroNode2.setLeft(heroNode4);
heroNode2.setRight(heroNode5);
heroNode3.setLeft(heroNode6);
// 1
// 3 6
//8 10 14
ThreadedBinaryTree tree = new ThreadedBinaryTree(root);
//线索化
tree.threadedNodes();
//以10号结点测试
System.out.println("left:"+heroNode5.getLeft() + ";right = "+heroNode5.getRight());
}
}
测试的话只能用打印左右结点的方法检测:对于结点10,它的前驱结点为3,后继结点为1
遍历中序线索二叉树
中序遍历:先遍历左子树,再输出父结点,再遍历右子树
但是因为我们的叶子结点线索化了,左右结点不为null,如果还用以前的遍历方法会造成死循环
根据left/rightType判断是否为子结点,决定是否遍历
在ThreadedBinaryTree添加遍历方法:
//遍历中序线索二叉树
public void threadedList(){
//设置一个变量,存储当前遍历的结点,从root开始
HeroNode node = root;
while (node != null){
//循环找到 left/rightType == 1 的结点
//当left/rightType == 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();
}
}
测试类:
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
HeroNode root = new HeroNode(1,"宋江");
HeroNode heroNode2 = new HeroNode(3,"吴用");
HeroNode heroNode3 = new HeroNode(6,"卢俊义");
HeroNode heroNode4 = new HeroNode(8,"林冲");
HeroNode heroNode5 = new HeroNode(10,"鲁智深");
HeroNode heroNode6 = new HeroNode(14, "燕青");
//创建二叉树
root.setLeft(heroNode2);
root.setRight(heroNode3);
heroNode2.setLeft(heroNode4);
heroNode2.setRight(heroNode5);
heroNode3.setLeft(heroNode6);
// 1
// 3 6
//8 10 14
ThreadedBinaryTree tree = new ThreadedBinaryTree(root);
//线索化
tree.threadedNodes();
System.out.println("== 遍历线索化二叉树 ==");
tree.threadedList();
}
}
遍历结果:8,3,10,1,14,6
,符合中序遍历