一.线索化二叉树和普通二叉树的区别
- 线索化二叉树,即给当前节点的左子节点为空或者右子节点为空的加一个指针指向当前节点的前驱或者后继(这样能充分利用节点的左右指针,遍历也方便,可以直接线性的遍历不需要递归)
- 1.左子节点为空,指向当前节点的前驱节点
- 2.右子节点为空,指向当前节点的后继节点
- 3.节点的顺序可以为(前序、中序、后序)遍历的顺序
比如,中序遍历结果为:{8,3,10,1,14,6}、前序:{1,3,8,10,6,14}、后序:{8,10,3,14,6,1}
3的前驱就是8,3的后继就是10
遍历线索化的二叉树最关键是判断当前节点是否有后继节点,有则继续输出后继节点,
没有则需要考虑当前节点的下一个节点是哪个.(结合前中后序遍历的规则)
二.线索化二叉树与遍历
前序
前序顺序:{1,3,8,10,6,14}
- 前序线索化
思路:根据前序遍历的规则,对当前节点的左右指针进行处理。
// 前序线索化
public void prefixThreadedBinaryTree(HeroNode root){
// null不能进行线索化
if (root==null){
System.out.println("二叉树为空!");
return;
}
// 对当前节点线索化,只对当前节点的左子节点或右子节点为空的节点线索化
if (root.left==null){
// 左子节点指向前驱节点
root.left = preNode;
root.leftType=1;
}
// 对于后继节点,当前节点是不知道它的后继是谁的,只能设置前一个节点的后继为当前节点
if (preNode!=null && preNode.right==null){
preNode.right = root;
preNode.rightType = 1;
}
// 即将结束对当前节点遍历,保存当前节点到preNode;
preNode = root;
// 遍历左子树
if (root.left!=null && root.leftType==0){
prefixThreadedBinaryTree(root.left);
}
// 遍历右子树
if (root.right!=null && root.rightType==0){
prefixThreadedBinaryTree(root.right);
}
}
- 遍历前序线索化的二叉树
思路:根据前序的规则,最先输出的节点一定是父节点,对于输出的当前节点考虑它是否有存储后继节点,有直接输出后继即可,没有则考虑它下一个节点应该是哪个,根据前序遍历的规则,它可能为当前节点的左子节点或当前节点的右子节点。
/**
* 遍历前序线索化二叉树(当前节点=》左子树=》右子树)
* 步骤 1 输出当前节点
* 步骤 2 判断当前节点右指针存储的是否是后继节点,有则继续输出后继节点,没有则=》步骤 3
* 步骤 3 判断是否有左子节点,有则以左子节点为当前节点 继续步骤 1、2、3,没有则=》步骤 4
* 步骤 4 只要不是最后一个节点,当前节点必定有右子节点,以该右子节点为当前节点继续步骤1、2、3
*
* @param root 根节点
*/
public void threadedPrefixOrder(HeroNode root){
if (root == null){
System.out.println("二叉树为空!");
return;
}
HeroNode curNode = root;
//
while (curNode!=null){
// 步骤 1 输出当前节点
System.out.println(curNode);
// 步骤 2 若当前节点有后继节点,则直接输出后继节点
while (curNode.rightType==1){
curNode = curNode.right;
System.out.println(curNode);
}
// 步骤 3 若当前节点没有后继节点,判断有无左子节点,有则以该左子节点为当前节点继续上面步骤
if (curNode.leftType==0){
curNode = curNode.left;
continue;
}
// 步骤 4 若没有左子节点则以右子节点为当前节点继续上面步骤
// (也有可能是遍历的最后一个节点,最后一个节没有后继节点,但也没有右子树,即结束循环)
curNode = curNode.right;
}
}
中序
中序顺序:{8,3,10,1,14,6}
- 中序线索化
思路:与前序线索化类似,这里就不多叙述了。
// 中序线索化
public void infixThreadedBinaryTree(HeroNode root){
// null不能进行线索化
if (root==null){
System.out.println("二叉树为空!");
return;
}
// 遍历左子树
if (root.left!=null && root.leftType==0){
infixThreadedBinaryTree(root.left);
}
// 对当前节点线索化,只对当前节点的左子节点或右子节点为空的节点线索化
if (root.left==null){
// 左子节点指向前驱节点
root.left = preNode;
root.leftType=1;
}
// 对于后继节点,当前节点是不知道它的后继是谁的,只能设置前一个节点的后继为当前节点
if (preNode!=null && preNode.right==null){
preNode.right = root;
preNode.rightType = 1;
}
// 即将结束对当前节点遍历,保存当前节点到preNode;
preNode = root;
// 遍历右子树
if (root.right!=null && root.rightType==0){
infixThreadedBinaryTree(root.right);
}
}
- 遍历中序线索化的二叉树
思路:根据中序的规则,第一个节点是最先输出而且一定是左子树的最后一个节点,因此,找到第一个节点,输出该节点,然后判断它是否有存储后继节点,有直接输出后继节点即可,若没有,则思考它下一个节点应该是哪个,根据中序的规则,当前节点没有存储后继节点那么它必定有右子树,那么该节点的位置应该是中间的父节点,此时以当前节点的右子节点为起始节点,找到以它为子树的第一个节点,然后继续上面步骤,判断是否存储后继…
/**
* 线索化中序遍历
* 线索化中序遍历的步骤(因为没有左子树或者右子树的节点(即左子节点或者右子节点为空)都存储着它的前驱节点或者后继节点,
* 因此,寻找当前节点的左子树的最后一个节点 因为 中序遍历的顺序是(遍历当前节点左子树=》当前节点=》遍历当前节点右子树))
* 步骤1.找到当前节点左子树最后一个节点(没有左子树(即左指针存储着前驱节点)就则跳到步骤2)=》
* 步骤2.输出当前节点=》
* 步骤3.判断当前节点是否有右子树
* 若有,找到当前节点的右子树(以该右子树的第一个节点重复上述1,2,3步骤)
* 若没有,没有右子树证明该节点的右指针存储着后继节点,此时以它的后继节点重复2,3步骤(即以该后继节点为当前节点重复2,3步骤)
* @param root 根节点
*/
public void threadedInfixOrder(HeroNode root){
if (root == null){
System.out.println("二叉树为空!");
return;
}
// 当前节点
HeroNode curNode = root;
// 当前节点为null结束循环.
while (curNode != null){
// 步骤 1
while (curNode.leftType==0){
curNode = curNode.left;
// 若结束循环即当前节点为左子树最后一个节点,该节点左子节点为null
}
// 步骤 2
System.out.println(curNode);
// 步骤 3
// 当前节点没有右子树,右子节点存储的是后继节点
while (curNode.rightType==1){
// 以该后继节点重复步骤2、步骤3
curNode = curNode.right;
System.out.println(curNode);
}
// 若当前节点有右子树(也有可能是遍历的最后一个节点,最后一个节没有后继节点,但也没有右子树,即结束循环)
curNode = curNode.right;
}
}
后序
后序顺序:{8,10,3,14,6,1}
- 后序线索化
思路:和前序类似,不多叙述。
// 后续线索化
public void postThreadedBinaryTree(HeroNode root){
// null不能进行线索化
if (root==null){
System.out.println("二叉树为空!");
return;
}
// 遍历左子树
if (root.left!=null && root.leftType==0){
postThreadedBinaryTree(root.left);
}
// 遍历右子树
if (root.right!=null && root.rightType==0){
postThreadedBinaryTree(root.right);
}
// 对当前节点线索化,只对当前节点的左子节点或右子节点为空的节点线索化
if (root.left==null){
// 左子节点指向前驱节点
root.left = preNode;
root.leftType=1;
}
// 对于后继节点,当前节点是不知道它的后继是谁的,只能设置前一个节点的后继为当前节点
if (preNode!=null && preNode.right==null){
preNode.right = root;
preNode.rightType = 1;
}
// 即将结束对当前节点遍历,保存当前节点到preNode;
preNode = root;
}
- 遍历后序线索化的二叉树
思路: 根据后序的规则,第一个节点必定是左子树的最后一个节点,输出该节点,然后判断该节点是否有存储后继节点,有就直接输出后继节点即可,没有则考虑它的后继节点应该是哪个,没有存储后继节点那么该节点有右子树。此时,有两种情况,
1.当前节点的右子节点是它的前驱节点也是,那么以该节点为根节点的子树已经遍历完成,根据后序的规则应该进入当前节点的父节点的右子树寻找当前节点的后继节点。
当前节点就是下图的节点3这种情况。
2.当前节点的前驱节点并不是上一个节点,以当前节点的右子节点为起始节点重复上述步骤,找第一个节点…
当前节点就是下图的节点1
/**
* 遍历后序线索化的二叉树
* curNode.parent是当前节点的父节点,因为当前节点没有存储后继节点时不能直接找到它的后继节点,需要根据父节点来找到后继节点。
* 步骤 1 找到当前节点的左子树的最后一个节点,即第一个节点,输出该节点
* 步骤 2 判断当前节点是否有存储后继节点,有则直接输出,没有则当前节点有右子树=》步骤 3
* 步骤 3 当前节点有右子树(右指针没有存储后继节点),此时分两种情况:
* 1.当前节点的前驱节点是上一个节点,并以当前节点的父节点重复步骤 2 3
* 2.当前节点的前驱节点不是上一个节点,以当前节点的右子节点为起始节点重复步骤 1 2 3
* @param root 根节点
*/
public void threadedPostOrder(HeroNode root){
if (root == null){
System.out.println("二叉树为空!");
return;
}
HeroNode curNode = root;
HeroNode preNode = null;
// 步骤 1
while (curNode.leftType==0){
curNode = curNode.left;
// 若结束循环即当前节点为左子树最后一个节点,该节点左子节点为null
}
// 从左子树最后一个节点开始
System.out.println(curNode);
while (curNode != null){
// 步骤 2
while (curNode.rightType == 1){
preNode = curNode;
curNode = curNode.right;
System.out.println(curNode);
}
// 步骤 3
if (curNode.right == preNode){
// 情况 1
preNode = curNode;
curNode = curNode.parent;
}else {
// 情况 2
curNode = curNode.right;
while (curNode.leftType==0){
curNode = curNode.left;
// 若结束循环即当前节点为左子树最后一个节点,该节点左子节点为null
}
// 从左子树最后一个节点开始
System.out.println(curNode);
}
}
}
三.全部代码:
/**
* @author fqs
* @Decription
* 线索化二叉树,即给当前节点的左子节点为空或者右子节点为空的加一个指针指向当前节点的前驱或者后继
* 1.左子节点为空,指向当前节点的前驱节点
* 2.右子节点为空,指向当前节点的后继节点
* 3.节点的顺序可以为(前序、中序、后序)遍历的顺序
* 比如,中序遍历结果为:{8,3,10,1,14,6}、前序:{1,3,8,10,6,14}、后序:{8,10,3,14,6,1}
* 3的前驱就是8,3的后继就是10
* 遍历线索化的二叉树最关键是判断当前节点是否有后继节点,有则继续输出后继节点,
* 没有则需要考虑当前节点的下一个节点是哪个.(结合前中后序遍历的规则)
* @since 2022/12/29
*/
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
HeroNode root = new HeroNode(1,"tom");
HeroNode node2 = new HeroNode(3,"jack");
HeroNode node3 = new HeroNode(6,"smith");
HeroNode node4 = new HeroNode(8,"mary");
HeroNode node5 = new HeroNode(10,"king");
HeroNode node6 = new HeroNode(14,"dim");
root.left = node2;
root.right = node3;
node2.parent = root;
node3.parent = root;
node2.left = node4;
node2.right = node5;
node4.parent = node2;
node5.parent = node2;
node3.left = node6;
node6.parent = node3;
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.root = root;
// 测试前序线索化二叉树
// threadedBinaryTree.prefixThreadedBinaryTree();
// System.out.println("no为10的节点的前驱:"+node5.left);
// System.out.println("no为10的节点的后继:"+node5.right);
// threadedBinaryTree.threadedPrefixOrder();
// 测试中序线索化二叉树
// threadedBinaryTree.infixThreadedBinaryTree();
// System.out.println("no为10的节点的前驱:"+node5.left);
// System.out.println("no为10的节点的后继:"+node5.right);
// 中序遍历输出线索化二叉树
// threadedBinaryTree.threadedInfixOrder();
// 测试后序线索化二叉树
threadedBinaryTree.postThreadedBinaryTree();
System.out.println("no为10的节点的前驱:"+node5.left);
System.out.println("no为10的节点的后继:"+node5.right);
threadedBinaryTree.threadedPostOrder();
}
}
class ThreadedBinaryTree{
public HeroNode root;
// 指向当前节点的前一个节点,用于设置前驱、后继节点
public HeroNode preNode;
public void prefixThreadedBinaryTree(){
prefixThreadedBinaryTree(root);
}
public void infixThreadedBinaryTree(){
infixThreadedBinaryTree(root);
}
public void postThreadedBinaryTree(){
postThreadedBinaryTree(root);
}
public void threadedPrefixOrder(){
threadedPrefixOrder(root);
}
public void threadedInfixOrder(){
threadedInfixOrder(root);
}
public void threadedPostOrder(){
threadedPostOrder(root);
}
// 前序线索化
public void prefixThreadedBinaryTree(HeroNode root){
// null不能进行线索化
if (root==null){
System.out.println("二叉树为空!");
return;
}
// 对当前节点线索化,只对当前节点的左子节点或右子节点为空的节点线索化
if (root.left==null){
// 左子节点指向前驱节点
root.left = preNode;
root.leftType=1;
}
// 对于后继节点,当前节点是不知道它的后继是谁的,只能设置前一个节点的后继为当前节点
if (preNode!=null && preNode.right==null){
preNode.right = root;
preNode.rightType = 1;
}
// 即将结束对当前节点遍历,保存当前节点到preNode;
preNode = root;
// 遍历左子树
if (root.left!=null && root.leftType==0){
prefixThreadedBinaryTree(root.left);
}
// 遍历右子树
if (root.right!=null && root.rightType==0){
prefixThreadedBinaryTree(root.right);
}
}
/**
* 遍历前序线索化二叉树(当前节点=》左子树=》右子树)
* 步骤 1 输出当前节点
* 步骤 2 判断当前节点右指针存储的是否是后继节点,有则继续输出后继节点,没有则=》步骤 3
* 步骤 3 判断是否有左子节点,有则以左子节点为当前节点 继续步骤 1、2、3,没有则=》步骤 4
* 步骤 4 只要不是最后一个节点,当前节点必定有右子节点,以该右子节点为当前节点继续步骤1、2、3
*
* @param root 根节点
*/
public void threadedPrefixOrder(HeroNode root){
if (root == null){
System.out.println("二叉树为空!");
return;
}
HeroNode curNode = root;
//
while (curNode!=null){
// 步骤 1 输出当前节点
System.out.println(curNode);
// 步骤 2 若当前节点有后继节点,则直接输出后继节点
while (curNode.rightType==1){
curNode = curNode.right;
System.out.println(curNode);
}
// 步骤 3 若当前节点没有后继节点,判断有无左子节点,有则以该左子节点为当前节点继续上面步骤
if (curNode.leftType==0){
curNode = curNode.left;
continue;
}
// 步骤 4 若没有左子节点则以右子节点为当前节点继续上面步骤
// (也有可能是遍历的最后一个节点,最后一个节没有后继节点,但也没有右子树,即结束循环)
curNode = curNode.right;
}
}
// 中序线索化
public void infixThreadedBinaryTree(HeroNode root){
// null不能进行线索化
if (root==null){
System.out.println("二叉树为空!");
return;
}
// 遍历左子树
if (root.left!=null && root.leftType==0){
infixThreadedBinaryTree(root.left);
}
// 对当前节点线索化,只对当前节点的左子节点或右子节点为空的节点线索化
if (root.left==null){
// 左子节点指向前驱节点
root.left = preNode;
root.leftType=1;
}
// 对于后继节点,当前节点是不知道它的后继是谁的,只能设置前一个节点的后继为当前节点
if (preNode!=null && preNode.right==null){
preNode.right = root;
preNode.rightType = 1;
}
// 即将结束对当前节点遍历,保存当前节点到preNode;
preNode = root;
// 遍历右子树
if (root.right!=null && root.rightType==0){
infixThreadedBinaryTree(root.right);
}
}
/**
* 线索化中序遍历
* 线索化中序遍历的步骤(因为没有左子树或者右子树的节点(即左子节点或者右子节点为空)都存储着它的前驱节点或者后继节点,
* 因此,寻找当前节点的左子树的最后一个节点 因为 中序遍历的顺序是(遍历当前节点左子树=》当前节点=》遍历当前节点右子树))
* 步骤1.找到当前节点左子树最后一个节点(没有左子树(即左指针存储着前驱节点)就则跳到步骤2)=》
* 步骤2.输出当前节点=》
* 步骤3.判断当前节点是否有右子树
* 若有,找到当前节点的右子树(以该右子树的第一个节点重复上述1,2,3步骤)
* 若没有,没有右子树证明该节点的右指针存储着后继节点,此时以它的后继节点重复2,3步骤(即以该后继节点为当前节点重复2,3步骤)
* @param root 根节点
*/
public void threadedInfixOrder(HeroNode root){
if (root == null){
System.out.println("二叉树为空!");
return;
}
// 当前节点
HeroNode curNode = root;
// 当前节点为null结束循环.
while (curNode != null){
// 步骤 1
while (curNode.leftType==0){
curNode = curNode.left;
// 若结束循环即当前节点为左子树最后一个节点,该节点左子节点为null
}
// 步骤 2
System.out.println(curNode);
// 步骤 3
// 当前节点没有右子树,右子节点存储的是后继节点
while (curNode.rightType==1){
// 以该后继节点重复步骤2、步骤3
curNode = curNode.right;
System.out.println(curNode);
}
// 若当前节点有右子树(也有可能是遍历的最后一个节点,最后一个节没有后继节点,但也没有右子树,即结束循环)
curNode = curNode.right;
}
}
// 后续线索化
public void postThreadedBinaryTree(HeroNode root){
// null不能进行线索化
if (root==null){
System.out.println("二叉树为空!");
return;
}
// 遍历左子树
if (root.left!=null && root.leftType==0){
postThreadedBinaryTree(root.left);
}
// 遍历右子树
if (root.right!=null && root.rightType==0){
postThreadedBinaryTree(root.right);
}
// 对当前节点线索化,只对当前节点的左子节点或右子节点为空的节点线索化
if (root.left==null){
// 左子节点指向前驱节点
root.left = preNode;
root.leftType=1;
}
// 对于后继节点,当前节点是不知道它的后继是谁的,只能设置前一个节点的后继为当前节点
if (preNode!=null && preNode.right==null){
preNode.right = root;
preNode.rightType = 1;
}
// 即将结束对当前节点遍历,保存当前节点到preNode;
preNode = root;
}
/**
* 遍历后序线索化的二叉树
* curNode.parent是当前节点的父节点,因为当前节点没有存储后继节点时不能直接找到它的后继节点,需要根据父节点来找到后继节点。
* 步骤 1 找到当前节点的左子树的最后一个节点,即第一个节点,输出该节点
* 步骤 2 判断当前节点是否有存储后继节点,有则直接输出,没有则当前节点有右子树=》步骤 3
* 步骤 3 当前节点有右子树(右指针没有存储后继节点),此时分两种情况:
* 1.当前节点的前驱节点是上一个节点,并以当前节点的父节点重复步骤 2 3
* 2.当前节点的前驱节点不是上一个节点,以当前节点的右子节点为起始节点重复步骤 1 2 3
* @param root 根节点
*/
public void threadedPostOrder(HeroNode root){
if (root == null){
System.out.println("二叉树为空!");
return;
}
HeroNode curNode = root;
HeroNode preNode = null;
// 步骤 1
while (curNode.leftType==0){
curNode = curNode.left;
// 若结束循环即当前节点为左子树最后一个节点,该节点左子节点为null
}
// 从左子树最后一个节点开始
System.out.println(curNode);
while (curNode != null){
// 步骤 2
while (curNode.rightType == 1){
preNode = curNode;
curNode = curNode.right;
System.out.println(curNode);
}
// 步骤 3
if (curNode.right == preNode){
// 情况 1
preNode = curNode;
curNode = curNode.parent;
}else {
// 情况 2
curNode = curNode.right;
while (curNode.leftType==0){
curNode = curNode.left;
// 若结束循环即当前节点为左子树最后一个节点,该节点左子节点为null
}
// 从左子树最后一个节点开始
System.out.println(curNode);
}
}
}
}
class HeroNode {
public int no;
public String name;
public HeroNode left;
public HeroNode right;
// 左子节点类型:0=》指向的左子树;1=》指向的是前驱节点
public int leftType;
// 右子节点类型:0=》指向的右子树;1=》指向的是后继节点
public int rightType;
// 存储当前节点的父节点,用于后序遍历线索化二叉树
public HeroNode parent;
public HeroNode() {
}
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
public boolean del(int no){
// 删除的是否是左子节点或者左子树
if (this.left != null && this.left.no == no){
this.left = null;
return true;
}
// 删除的是否是右子节点或者右子树
if (this.right != null && this.right.no == no){
this.right = null;
return true;
}
// 向左子树查找目标删除
if (this.left!=null && this.left.del(no)){
return true;
}else return this.right != null && this.right.del(no);
// 向右子树查找目标删除
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'\'' +
'}';
}
}