我所知道的数据结构之线索化二叉树

本文深入探讨了线索化二叉树的概念,解释了如何利用空指针域构建线索,以方便中序遍历。通过示例展示了如何将给定数列构建成二叉树并进行中序线索化,分析了线索化过程中的关键步骤,并提供了相关代码实现。线索化后,可以避免递归遍历,提高遍历效率。文章最后给出了遍历线索化二叉树的方法及其运行结果。
摘要由CSDN通过智能技术生成

上一篇我们学习顺序存储二叉树,这时出现一个问题,我们来看看

将数列{1,3,6,8,10,14},构建成一颗二叉树

image.png

这时候我们发现一些问题,一起进行分析看看
1.当我们对二叉树进行中序遍历时,数列为{8,3,10,1,14,6}
2.6,8,10,14节点的左右指针没有充分利用
3.遍历二叉树时除了使用递归遍历二叉树,那么有没有其他方法呢?
4.希望利用各个节点左右指针,直接指向左右节点怎么解决?

一、什么是线索化二叉树

针对于前面的问题我们聊聊线索化二叉树

根据上图所示,我们发现如果使用二叉链表展示二叉树就会发现n个节点中会含有n+1个空指针域,即上图有七个空指针域

公式:2n - (n-1) = n+1

因为一共有2n个指针,除了根节点,每个节点都被自指针引用

我们来看看是不是这样?如下图所示

image.png

线索二叉树基本介绍

1.利用二叉表空指针域,存放指向该结点在某种遍历次序下的前驱与后续节点的指针称为线索

2.这种加上了线索的二叉链表称为线索链表,相应的二叉树也称为线索二叉树,根据性质不同分别有前序、中序、后序等线索二叉树

3.一个结点的前一个节点,称为前驱节点

4.一个结点的后一个节点,称为后续节点

二、通过示例认识线索化二叉树

示例应用案例说明:

将下面的二叉树进行中序线索二叉树,中序结果为{8,3,10,1,14,6}

image.png

应用示例图解分析

中序遍历结果:{8,3,10,1,14,6}进行线索化,该怎么操作?

我们发现[节点八]前驱节点Null后继节点[节点三],要关联

[节点三]前驱节点[节点八]后继节点[节点十],但[节点三]已指向两子节点,则无需关联

[节点十]前驱节点[节点三],要关联后继节点[节点一],要关联

[节点一]前驱节点[节点十]后继节点[节点十四],但[节点一]已指向两子节点,则无需关联

[节点十四]前驱节点[节点一],要关联后继节点[节点六],要关联

[节点六]前驱节点[节点十四]后继节点Null,则无需关联

image.png

线索化问题发现

那么有没有发现,如果这样安排关联就会发现[节点一]的一些问题

1.前驱节点应该是[节点十],但[节点一]并没有指向[节点十]

2.后继节点应该是[节点十四],但[节点一]并没有指向[节点十四]

left、right属性的情况说明

线索化二叉树后,Node节点的属性left、right 会有以下情况

①:[left指向的是左子树,也可能是指向的前驱节点],比如[节点一]的left指向的左子树,而[节点十]的left指向的就是前驱节点.

image.png

②:[right指向的是右子树,也可能是指向后继节点],比如[节点一]right指向的是右子树,而[节点十]的right指向的是后继节点.

image.png

所以我们需要加多[标志Type]代表当前是指向子树、还是节点

应用示例思路分析

同时我们进行中序线索化,现在各位小伙伴还记得二叉树遍历方式吗?

  • 前序是先输出父节点,再遍历左子树和右子树
  • 中序是先遍历左子树,再输出父节点,最后遍历右子树
  • 后序是先遍历左子树,再遍历右子树,最后输出父节点

image.png

所以中序思路步骤分:先线索化左子树、再线索化当前节点、最后线索化右子树

当我们线索化[某个节点node]时,其实需要一个[前驱节点pre]构成关系关联,这样才有可能实现线索化。如图所示

image.png

现在我们来分析[线索化前驱节点的思路]:队列的[节点八]

image.png

每处理完后,记得让当前node成为下一个节点的前驱节点:pre = node

现在我们来分析[线索化后继节点的思路]:队列的[节点八]

image.png

现在我们根据思路定义节点HeroNode 信息

//创建HerNode
class HeroNode {

    private int no;
    private String name;

    private HeroNode left;  //默认null 左节点
    private HeroNode right; //默认null 右节点

    /**
     * 说明
     * 1. leftType == 0 表示指向的是左子树
     * 2. leftType == 1 表示指向的是前驱节点
     *
     * 3.rigthType == 0 表示指向的是右子树
     * 4.rightType == 1 标识指向的是后续节点
     */
    private int leftType;

    private int rightType;

    public int getLeftType() {
        return leftType;
    }

    public void setLeftType(int leftType) {
        this.leftType = leftType;
    }

    public int getRightType() {
        return rightType;
    }

    public void setRightType(int rightType) {
        this.rightType = rightType;
    }

    public HeroNode(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode getLeft() {
        return left;
    }

    public void setLeft(HeroNode left) {
        this.left = left;
    }

    public HeroNode getRight() {
        return right;
    }

    public void setRight(HeroNode right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "HeroNode [no =" + no +", name =" + name +"]";
    }
}

创建一颗线索化二叉树ThreadedBinaryTree 信息

class ThreadedBinaryTree{

    private HeroNode root;

    //为了实现线索化,需要创建要给指向当前节点的前驱节点的指针
    //在进行递归线索化时,pre总是保存前一个节点
    private HeroNode prenode = null;

    public void setRoot(HeroNode root) {
        this.root = root;
    }

    public void setPrenode(HeroNode prenode) {
        this.prenode = prenode;
    }

    public void threadedNodes(){
        this.threadedNodes(root);
    }

    //编写对二叉树进行中序线索化方法
    private void threadedNodes(HeroNode node){

        //如果node == null 则不进行线索化

        if(node == null){
            return;
        }

        //中序遍历的规律:先遍历左子树,再输出父节点,再遍历右子树

        //(一)先线索化左子树
        threadedNodes(node.getLeft());
        //(二)线索化当前节点
        //比如说中序队列:{8,3,10,1,14,6}
        //先处理[节点8] 那么就要处理节点的前驱节点,判断是否左子树
        if(node.getLeft() == null ){
            //当前节点左指针指向前驱节点
            node.setLeft(prenode);
            node.setLeftType(1);//1 表示指向的是前驱节点  0 表示指向的是左子树
        }
        //处理后续节点
        if(prenode!=null && prenode.getRight() == null){
            //让前驱节点的右指针指向当前节点
            prenode.setRight(node);
            //修改前驱的右指针类型
            prenode.setRightType(1);
        }
        //处理完每个节点的操作后,让当前node是下一个节点的前驱节点
        prenode = node;

        //(三)再线索化右子树
        threadedNodes(node.getRight());
    }
}

现在让我们测试看看线索化后[节点十]是否已发生改变,看看是否成功

image.png

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.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        //创建二叉树
        ThreadedBinaryTree tree =new ThreadedBinaryTree();
        tree.setRoot(root);
        tree.threadedNodes();

        //测试节点十的前驱代码与后续节点是否改变
        HeroNode leftNode = node5.getLeft();
        HeroNode rigthNode = node5.getRight();
        System.out.println("[节点十]的前驱节点是" + leftNode);
        System.out.println("[节点十]的后续节点是" + rigthNode);

    }

}

运行结果如下:
[节点十]的前驱节点是HeroNode [no =3, name =jack]
[节点十]的后续节点是HeroNode [no =1, name =tom]

三、遍历中序线索化二叉树

image.png

因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用。

这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历。

因此无需使用递归方式,这样也提高了遍历的效率,遍历的次序应当和中序遍历保持一致。

也就说遍历中序线索化二叉树输出的结果也要是:{8,3,10,1,14,6}

class ThreadedBinaryTree{

    //遍历线索化二叉树的方法
    public void threadedList(){
        //定义一个变量,存储当前遍历的结点,从root开始
        HeroNode node = root;
        while(node != null) {
            //循环的找到leftType == 1的结点,第一个找到就是8结点
            //后面随着遍历而变化,因为当leftType==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) {
        //省略..
        tree.threadedList();
    }

}

运行结果如下:
HeroNode [no =8, name =mary]
HeroNode [no =3, name =jack]
HeroNode [no =10, name =king]
HeroNode [no =1, name =tom]
HeroNode [no =14, name =dim]
HeroNode [no =6, name =smith]

我们就要先找到[节点八],它的特点是leftType = 1

image.png

找到后输出这个节点

image.png

如果当前节点的右指针也是后继节点则替换成该后也输出

image.png

若当前节点的右指针不满足后继节点则替换往下走

image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值