线索化二叉树
基本说明
1、二叉链表中,n个节点,含有2n个指针;除根节点外,每个节点都要一个指针引用,总共需要n-1个指针;含2n-(n-1)=n+1个空指针域
2、利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针,这种附加的指针称为"线索"
(1)二叉树进行遍历,遍历后的顺序;前驱节点:一个节点的前一个节点;后继节点:一个节点的后一个节点;前序、中序、后序遍历产生的前驱节点、后续节点可能不同
(2)这种加上了线索的二叉链表称为线索链表, 相应的二叉树称为线索二叉树
(3)根据线索性质的不同, 线索二叉树可分为前序线索二叉树、中序线索二叉树、后序线索二叉树
3、节点的left可能指向左子树,也可能指向前驱节点;节点的right可能指向右子树,也可能指向后续节点
4、前驱节点:一个节点的前一个节点
5、后继节点:一个节点的后一个节点
一棵左右子树不空的二叉树在线索化后,其空指针域数
1、前序线索化,空指针数 = 1,位置:在最右下叶子的右指针
2、中序线索化,空指针数 = 2,位置:在最左下叶子的左指针和最右下叶子的右指针
3、后续线索化,空指针数 = 1,位置:在最左下叶子的左指针
线索二叉树的遍历
1、线索化后,各个结点指向有变化,原来的遍历方式不能使用,需要使用新的方式遍历线索化二叉树,各个节点可以通过线性方式遍历,无需使用递归方式,提高了遍历的效率,哪种线索化就对应哪种遍历次序
2、实现思路(中序遍历)
(1)定义一个指针,初始指向root,找最深层的具有前驱结点的左节点,并打印
(2)沿着左节点遍历打印其后继节点,直到没有后继节点,指针指向当前步骤最后一个后继节点
(3)以指针指向其右节点作为新的起始节点,继续找当前步骤最深层的具有前驱节点的左节点
(4)直到最后得到的右节点为 null
二叉树在线索化后
1、仍不能有效求解的问题
(1)先序线索二叉树找先序前驱
(2)后序线索二叉树找后序后继
2、原因
(1)前序遍历(根左右),最先访问的子树的根节点,子树根节点的两个指针域都指向子树了,所以不能空出来存放前驱线索;最后访问的节点都有空的右指针域,以指向后继
(2)中序遍历(左根右),最先访问的节点都有空的左指针域,以指向前驱;最后访问的节点都有空的右指针域,以指向后继
(3)后续遍历(左右根),最先访问的节点都有空的左指针域,以指向前驱;最后访问的子树的根节点,子树根节点的两个指针域都指向子树了,所以不能空出来存放后继线索
代码实现
public class ThreadBinaryTree {
public Node root;
public Node pre = null;//创建一个指向当前节点的前驱节点的指针,在递归进行线索化时,pre总是保留前一个节点
//重载postThread方法
public void postThread() {
this.postThread(root);
}
//后序线索化
public void postThread(Node node) {
if (node == null) {//node == null,不能线索化
return;
}
if (node.left != null) {
node.left.parent = node;
}
if (node.right != null) {
node.right.parent = node;
}
postThread(node.left);//向左递归线索化左子树
postThread(node.right);//向右递归线索化右子树
//线索化当前节点
if (node.left == null) {//处理前驱节点
node.left = pre;//让当前节点的左指针指向前驱节点
node.leftType = 1;//修改当前节点的左指针的类型
}
if (pre != null && pre.right == null) {//处理后继节点
pre.right = node;//让前驱节点的右指针指向当前节点
pre.rightType = 1;//修改前驱节点的右指针类型
}
pre = node;//每处理一个节点后,让当前节点是下一个结点的前驱节点
}
//遍历后序线索化二叉树
public void postList() {
Node node = root;//定义一个变量,存储当前遍历的结点,从root开始
while (node != null && node.leftType == 0) {//从整棵树最深处的左节点开始
node = node.left;
}
while (node != null) {
if (node.leftType == 1) {
System.out.println(node);
pre = node;
node = node.right;
} else if (node.right == pre) {//若上个处理的节点是当前节点的右节点,说明node是该子树的根节点
System.out.println(node);//打印该子树的根节点
if (node == root) {//检查遍历完成的是子树还是整棵树
return;
}
pre = node;//pre指向该子树的根节点
node = node.parent;//node指向其父节点
} else {//node所在的子树遍历完成,即其父节点左边子树(不包括父节点)
node = node.right;//父节点的右边子树的根节点
while (node != null && node.leftType == 0) {//找右边子树最深层的左节点
node = node.left;
}
}
}
}
//重载preThread方法
public void preThread() {
this.preThread(root);
}
//前序线索化
public void preThread(Node node) {
if (node == null) {//node == null,不能线索化
return;
}
//线索化当前节点
if (node.left == null) {//处理前驱节点
node.left = pre;//让当前节点的左指针指向前驱节点
node.leftType = 1;//修改当前节点的左指针的类型
}
if (pre != null && pre.right == null) {//处理后继节点
pre.right = node;//让前驱节点的右指针指向当前节点
pre.rightType = 1;//修改前驱节点的右指针类型
}
pre = node;//每处理一个节点后,让当前节点是下一个结点的前驱节点
//只有指向子树才进入,否则死循环
if (node.leftType == 0) {
preThread(node.left);//向左递归线索化左子树
}
if (node.rightType == 0) {
preThread(node.right);//向右递归线索化右子树
}
}
//遍历前序线索化二叉树
public void preList() {
Node node = root;//定义一个变量,存储当前遍历的结点,从root开始
//存在左子节点就往左走,否则往右走,此时右指针一定是前序遍历的下一个节点
while (node != null) {
System.out.println(node);
if (node.leftType == 0) {
node = node.left;
} else {
node = node.right;
}
}
}
//重载infixThread方法
public void infixThread() {
this.infixThread(root);
}
//中序线索化
public void infixThread(Node node) {
if (node == null) {//node == null,不能线索化
return;
}
infixThread(node.left);//向左递归线索化左子树
//线索化当前节点
if (node.left == null) {//处理前驱节点
node.left = pre;//让当前节点的左指针指向前驱节点
node.leftType = 1;//修改当前节点的左指针的类型
}
if (pre != null && pre.right == null) {//处理后继节点
pre.right = node;//让前驱节点的右指针指向当前节点
pre.rightType = 1;//修改前驱节点的右指针类型
}
pre = node;//每处理一个节点后,让当前节点是下一个结点的前驱节点
infixThread(node.right);//向右递归线索化右子树
}
//遍历中序线索化二叉树
public void infixList() {
Node node = root;//定义一个变量,存储当前遍历的结点,从root开始
while (node != null) {
while (node.leftType == 0) {//找最深层的左节点
node = node.left;
}
System.out.println(node);
while (node.rightType == 1) {//若当前节点有后继节点
node = node.right;//node指向该子树的根节点
System.out.println(node);//打印该(子)树的根节点
}//退出循环时,node的左边子树遍历完成(包括node)
node = node.right;//node指向其右节点作为新的起始节点,即遍历node的右边子树
}
}
}
class Node {
public int no;
public String name;//视作data
public Node left;//默认null
public Node right;//默认null
public Node parent;//后序线索化及遍历使用,指针指向父节点
public int leftType;//等于0,指向左子树;等于1,指向前驱节点
public int rightType;//等于0,指向右子树;等于1,指向后续节点
public Node(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战