算法系列之线索化二叉树,前序线索化中序线索化后序线索化以及遍历~
Posted Roninaxious
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法系列之线索化二叉树,前序线索化中序线索化后序线索化以及遍历~相关的知识,希望对你有一定的参考价值。
1.何谓线索化二叉树
🏴☠️一般的二叉树的叶子节点的指针都造成了浪费,对于有n个节点的二叉树,其中就有n+1个空链域;所以我们可以将空链域存放某种遍历次序下该节点的前驱节点和后继节点,这些指针被成为线索,加上线索的二叉树称为线索化二叉树。
🍁例如上图中二叉树,它的前序遍历之后的结果为[1,2,4,8,9,5,10,3,6,7],那么对于叶子节点8的前驱节点就是4、后继节点就是9。对于叶子节点7比较例外,它没有后继节点。
2.线索化二叉树的本质
💌二叉树遍历的本质是将一个非线性结构,转换为线性结构,使得每个节点都有唯一前驱和后继节点(第一个节点无前驱,最后一个节点无后序),这是二叉树遍历的本质;对于二叉树的某一个节点而言,找到它的后继节点是非常容易的,但它的前驱节点只有在遍历中才能得到。为了解决这种问题,有两种方法。
🍅1.增加向前的指针,这种方式增加了存储开销,不可取。
🍅2.利用空链指针实现,这也就是线索化。
3.线索化二叉树的存储结构
🏴☠️对于二叉树中的某个节点,我们怎么知道它指向的是左右指针还是前驱后继节点,所以需要加入两个变量leftType和rightType,以leftType为例,当leftType ==0 时指向的是左指针,当leftType == 1时指向的是前驱节点。
data | left | right | leftType | rightType |
---|---|---|---|---|
数据 | 左指针 | 右指针 | 左指针指向类型 | 右指针指向类型 |
public int id;
public String name;
public HeroNode left;
public HeroNode right;
public int leftType = 0;
public int rightType = 0;
4.构建线索化二叉树
📚对于构建线索化二叉树本质上也就是遍历二叉树,在遍历过程中,增加检测当前节点的左右节点是否为空;将它们改为指向前驱和后继节点的线索,为了实现这个过程,需要添加一个pre指针,将pre指针每次指向上次访问的节点。然后就可以使用pre指向它的前驱,使用当前访问的节点指向pre也就是后继。
4.1.先序线索化
/**
* 前序线索化二叉树
* 类似于中序线索化二叉树
* 思路:当满足条件temp.left ==null时,开始进行递归回溯,此时使用pre指针每次都在temp指针的前一个节点
*
* @param temp
*/
public void preThreadedBinaryTree(HeroNode temp) {
if (temp == null) {
return;
}
if (temp.left == null) {
temp.left = pre;
temp.leftType = 1;
}
if (pre != null && pre.right == null && pre.left != temp) {
pre.right = temp;
pre.rightType = 1;
}
pre = temp;
if (temp.leftType == 0) { //防止陷入无限循环当中
preThreadedBinaryTree(temp.left);
}
if (temp.rightType == 0)
preThreadedBinaryTree(temp.right);
}
📑需要注意的是需要判断leftType和rightType是否等于0,因为前面已经进行了部分线索化,所以不进行判断就会陷入无线循环当中。
4.2.中序线索化
/**
* 中序线索化二叉树,对于叶子节点,左右指针都为null,所以为了避免浪费,将其分别保存它的前驱和后继节点的地址
* 对于[1,2,3,4,5,6,7],中序遍历之后是[4,2,5,1,6,3,7],对于叶子节点5,它的左右指针都为null,所以它的左指针应该存储它的前驱节点2的地址,
* 它的右指针应该存储1节点的地址
*
* @param cur 根指针
*/
public void infixThreadedBinaryTree(HeroNode cur) {
if (cur == null) {
return;
}
infixThreadedBinaryTree(cur.left);
if (cur.left == null) {
cur.left = pre;
cur.leftType = 1;
}
if (pre != null && pre.right == null) {
pre.right = cur;
pre.rightType = 1;
}
pre = cur;
infixThreadedBinaryTree(cur.right);
}
4.3.后序线索化
/**
* 后序线索化二叉树
*
* @param node root
*/
public void sufThreadedBinaryTree(HeroNode node) {
if (node == null) {
return;
}
if (node.leftType == 0) {
sufThreadedBinaryTree(node.left);
}
if (node.rightType == 0) {
sufThreadedBinaryTree(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;
}
5.遍历线索化二叉树
📚对于普通的遍历二叉树已经不适用与线索化二叉树了,因为每个节点要么指向它的左右子节点,要么指向前驱后继节点,所以使用null作为判断条件已经不再合适;根据前文增加的leftType和rightType可以作为递归终止条件。
5.1.先序遍历 先序线索化二叉树
public void preThreadedErgodic(HeroNode cur) {
if (cur == null) {
return;
}
if (cur.leftType == 1 || cur.rightType == 1) {
System.out.print(cur.id + "->");
return;
}
System.out.print(cur.id + "->");
preThreadedErgodic(cur.left);
preThreadedErgodic(cur.right);
}
5.2.中序遍历 中序线索化二叉树
/**
* 中序遍历 中序线索化二叉树
*
* @param cur
*/
public void infixThreadedErgodic(HeroNode cur) {
if (cur == null) {
return;
}
if (cur.leftType == 1) {
System.out.print(cur.id + "->");
return;
}
infixThreadedErgodic(cur.left);
System.out.print(cur.id + "->");
infixThreadedErgodic(cur.right);
}
5.3.后序遍历 后序线索化二叉树
/**
* 遍历 后序列线索化二叉树
* @param cur
*/
public void sufThreadedErgodic(HeroNode cur) {
if (cur == null || cur.rightType == 1) {
if (cur != null) {
System.out.print(cur.id + "->");
}
return;
}
sufThreadedErgodic(cur.left);
sufThreadedErgodic(cur.right);
if (cur.leftType == 1) {
System.out.print(cur.id + "->");
return;
}
System.out.print(cur.id + "->");
}
6.线索化二叉树的优势与劣势
🎈优势
💦(1)利用线索二叉树进行中序遍历时,不必采用堆栈处理,速度较一般二叉树的遍历速度快,且节约存储空间。
💦(2)任意一个结点都能直接找到它的前驱和后继结点
🎈劣势
💦(1)结点的插入和删除麻烦,且速度也较慢。
💦(2)线索子树不能共用。
以上是关于算法系列之线索化二叉树,前序线索化中序线索化后序线索化以及遍历~的主要内容,如果未能解决你的问题,请参考以下文章