数据结构-线索化二叉树

1、什么是线索二叉树

线索二叉树是二叉树的一种,我们先看一个二叉树存储例子。

在这里插入图片描述
上图中的 # 是空的,会浪费一部分空间,如果我们希望将这些空的指针充分利用,让各个节点都可以指向自己的前驱结点和后继节点,那就需要使用到线索二叉树。

现有一颗节点数为 n 的二叉树,采用二叉链表的形式存储。对于每个节点均有指向左右孩子的两个指针域,二节点为 n 的二叉树一共有 n-1 个条有效分支路径。那么,则二叉链表中存在 2n-(n-1)=n+1个空指针域。那么这些空指针造成空间浪费。


此外,当对二叉树进行中序遍历时可以得到二叉树的中序序列。例如上图中二叉树的中序遍历结果为:DBEAGFC,可以得知 E 的前驱结点是 B,后继节点是 A。但是这种关系是在中序遍历完成之后得到的。


线索化二叉树分为三种:
在这里插入图片描述

上述情况是建立在遍历过的基础上,在二叉链表上,我们只能知道每个节点指向其左右孩子节点的地址,而不知道每个节点的前驱和后继,要想知道,必须遍历一次,以后每次需要知道时,都必须先遍历一次,太耗费时间。我们可以考虑在创建时就记住这些前驱和后继。这个时候我们就可以考虑利用那些空地址,存放指向节点在某种遍历次序下的前驱和后继节点的地址。这种指向前驱和后继的指针称为线索,所以叫线索二叉树。总结一下:

  • 若当前节点左指针域非空时,保留原指针不变;若左指针域为空时,添加该节点在相应遍历序列中前驱节点地址。
  • 若当前结点右指针域非空时,保留原指针不变;若右指针域为空时,添加该节点在相应遍历序列中后继节点地址。

这里要注意一下,我们怎么判断左/右指针域指向的是原指针还是前驱/后继节点?
在这里插入图片描述

2、中序线索化二叉树

中序线索化二叉树例子:
在这里插入图片描述

2.1 中序线索化二叉树实现

线索化:中序线索化二叉树,就按中序遍历,遍历的过程中在当前节点的左或右空指针域中添加前驱或后继节点。为了保留遍历过程中访问节点的前驱与后继关系,需要设置一个前一节点变量 pre 始终指向刚访问过的节点。

  • 如果当前节点 node.getLeft() == null (左子节点为空),则node.setLeftType(1) ; node.setLeft(pre) ; (左指针域指向前驱节点,修改 leftType 为线索模式)。

  • 如果前一节点pre.getRight == null (右子结点为空),则pre.setLeftType(1) ; pre.setRight(node) ; (右指针域指向后继节点,修改 rightType 为线索模式)。

💻 代码实现

package com.yao.tree;

/**
 * @Author: Yao
 * @Date: 2021/10/20 15:06
 */
public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {

        //测试
        HeroNode3 root = new HeroNode3(1, "tom");
        HeroNode3 node2 = new HeroNode3(3, "jack");
        HeroNode3 node3 = new HeroNode3(6, "mary");
        HeroNode3 node4 = new HeroNode3(8, "jon");
        HeroNode3 node5 = new HeroNode3(10, "mali");
        HeroNode3 node6 = new HeroNode3(14, "hh");

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree(root);
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        //进行中序线索化
        System.out.println("-----------------进行中序线索化----------------");
        threadedBinaryTree.threadedBinaryTreeMid(root);
        //测试 10 节点
        System.out.println(node5.getLeft()+" --> "+node5.getLeftType());
        System.out.println(node5.getRight()+" --> "+node5.getRightType());
    }
}

/*
ThreadedBinaryTree 线索化二叉树
 */
class ThreadedBinaryTree{

    private HeroNode3 root;

    //为了实现线索化二叉树,需要创建一个指向当前节点的前驱结点的指针
    private HeroNode3 per;

    public ThreadedBinaryTree(HeroNode3 root) {
        this.root = root;
    }

    /**
     * 中序线索化二叉树
     * @param node 需要线索化的节点
     */
    public void threadedBinaryTreeMid(HeroNode3 node){
        if (node == null){
            return;
        }
        //进行中序线索化
        //线索化左子树
        if (node.getLeft() != null) {
            threadedBinaryTreeMid(node.getLeft());
        }
        //线索化当前节点
        //处理当前节点的前驱结点
        if (node.getLeft() == null){
            //让当前节点的左指针指向前驱结点
            node.setLeft(per);
            //设置左指针的类型为前驱结点
            node.setLeftType(1);
        }
        //处理当前节点的后继节点
        if (per != null && per.getRight() == null){
            per.setRight(node);
            per.setRightType(1);
        }

        per = node;

        if (node.getRight() != null){
            //线索化右子树
            threadedBinaryTreeMid(node.getRight());
        }
    }
}

//HeroNode3
class HeroNode3{

    private int no;

    private String name;

    private HeroNode3 left;

    private HeroNode3 right;

    //说明 如果 leftType == 0 表示leftType指向的是左子树,leftType == 1,表示指向的是 前驱结点
    private int leftType;
    //说明 如果 rightType == 0 表示rightType指向的是右子树,rightType == 0,表示指向的是 后继节点
    private int rightType;

    public HeroNode3(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 HeroNode3 getLeft() {
        return left;
    }

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

    public HeroNode3 getRight() {
        return right;
    }

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

    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;
    }

    @Override
    public String toString() {
        return "HeroNode3{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

2.2 中序线索化二叉树遍历实现

中序二叉树遍历主要分为两步:

① 获取中序遍历的首节点。
② 获取中序线索化二叉树中当前节点的后继节点。

只要完成这两步就可以遍历中序线索化二叉树。


如何获取中序遍历的首节点:从根节点开始沿着左指针不断向左下搜索,直到找到最左的节点。最左的节点指该节点左指针为空。


获取中序线索化二叉树中当前节点的后继节点:有两种情况

①当前节点存在右子树:则从该右子树根节点开始不断向左下搜索,找到找到最左的节点。该节点即当前节点的后继。
②当前节点不存在右子树:则其后继节点即为其右指针域中后继线索所指节点。


思路分析:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

💻 代码实现

/**
     * 遍历中序线索化二叉树
     * @param node 根节点
     */
    public void threadedBinaryTreeMidList(HeroNode3 node){
        if (node == null){
            return;
        }
        HeroNode3 temp = node.getLeft();

        if (temp == null){
            return;
        }
        //1、寻找中序遍历的第一个节点
        while (temp.getLeft() != null && temp.getLeftType() == 0){
            temp = temp.getLeft();
        }

        //2、获取中序线索化二叉树中当前节点的后继节点
        while (temp != null){
            System.out.println(temp);
            //2.1 如果当前节点存在右子树,就一直向左下递归,找到最左的节点,就是当前节点的后继节点
            if (temp.getRightType() == 0 && temp.getRight() != null){
                temp = temp.getRight();
                while (temp.getLeft() != null && temp.getLeftType() == 0){
                    temp = temp.getLeft();
                }
            }else{
                //2.2 如果当前节点不存在右子树,就直接输出后继节点
                temp = temp.getRight();
            }
        }
    }

3、先序线索化二叉树

先序线索化二叉树例子
在这里插入图片描述

3.1 先序线索化二叉树实现

线索化:和中序线索化实现一样(把中序遍历改为先序遍历)。
因为遍历的顺序不一样,所以需要注意的先序和中序有个地方不一样,先序我们要判断是否是左子树/右子树在进行递归,不然会出现死递归


  • 判断是否是左子树(即不是前驱节点):先序的遍历顺序是"根左右",在对"左"节点进行遍历的时候当"左"节点没有左子树时,“左"节点的前驱节点就设置为"根"节点,此时在遍历”左“节点的"左"节点,就又会遍历"根节点”,就会形成死循环。

如上图中的 D.getLeft() 是前驱节点不是左子树,如果不判断就会在像B和D节点这种节点上死循环根左根左根左…,一直输出BDBDBD…。


  • 判断是否是右子树(即不是后继节点):同理如果不判断就会进入死递归

💻 代码实现

package com.yao.tree;

/**
 * @Author: Yao
 * @Date: 2021/10/20 15:06
 */
public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {

        //测试
        HeroNode3 root = new HeroNode3(1, "tom");
        HeroNode3 node2 = new HeroNode3(3, "jack");
        HeroNode3 node3 = new HeroNode3(6, "mary");
        HeroNode3 node4 = new HeroNode3(8, "jon");
        HeroNode3 node5 = new HeroNode3(10, "mali");
        HeroNode3 node6 = new HeroNode3(14, "hh");

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree(root);
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);



        //进行先序线索化
        System.out.println("-----------------进行先序线索化----------------");
        threadedBinaryTree.threadedBinaryTreePre(root);
        //测试 10 节点
        System.out.println(node5.getLeft()+" --> "+node5.getLeftType());
        System.out.println(node5.getRight()+" --> "+node5.getRightType());

    }
}

/*
ThreadedBinaryTree 线索化二叉树
 */
class ThreadedBinaryTree{

    private HeroNode3 root;

    //为了实现线索化二叉树,需要创建一个指向当前节点的前驱结点的指针
    private HeroNode3 per;

    public ThreadedBinaryTree(HeroNode3 root) {
        this.root = root;
    }

    /**
     * 先序线索化二叉树
     * @param node 需要线索化的节点
     */
    public void threadedBinaryTreePre(HeroNode3 node){
        if (node == null){
            return;
        }
        //线索化当前节点
        if (node.getLeft() == null){
            node.setLeft(per);
            node.setLeftType(1);
        }

        if (per != null && per.getRight() == null){
            per.setRight(node);
            per.setRightType(1);
        }

        per = node;

        if (node.getLeftType() == 0){  //如果不进行判断,就会进入死递归
            //线索化左子树
            threadedBinaryTreePre(node.getLeft());
        }

        if (node.getRightType() == 0){
            //线索化右子树
            threadedBinaryTreePre(node.getRight());
        }
    }
}

//HeroNode3
class HeroNode3{

    private int no;

    private String name;

    private HeroNode3 left;

    private HeroNode3 right;

    //说明 如果 leftType == 0 表示leftType指向的是左子树,leftType == 1,表示指向的是 前驱结点
    private int leftType;
    //说明 如果 rightType == 0 表示rightType指向的是右子树,rightType == 0,表示指向的是 后继节点
    private int rightType;

    public HeroNode3(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 HeroNode3 getLeft() {
        return left;
    }

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

    public HeroNode3 getRight() {
        return right;
    }

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

    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;
    }

    @Override
    public String toString() {
        return "HeroNode3{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

3.2 先序线索化二叉树遍历实现

从根节点一直遍历到最左节点(根节点没有左节点的话,根节点就是"最左节点"),最左节点的右节点(右子树或后继节点)就是下一个要遍历的节点,一直重复这个步骤,先序线索二叉树的遍历就完成了。

思路分析:

在这里插入图片描述
在这里插入图片描述

💻 代码实现

/**
     * 遍历先序线索化二叉树
     * @param node 根节点
     */
    public void threadedBinaryTreePreList(HeroNode3 node){
        if (node == null){
            return;
        }
        //遍历前序线索化二叉树比较简单,从根节点一直遍历到最左节点,当前节点的后继节点就是下一个要遍历的节点
        while (node != null){
            System.out.println(node);
            if (node.getLeft() != null && node.getLeftType() == 0){
                node = node.getLeft();
            }else {
                node = node.getRight();
            }
        }
    }

4、后序线索化二叉树

后序线索化二叉树例子
在这里插入图片描述

4.1 后序线索化二叉树实现

线索化:和中序线索化实现一样(把中序遍历改为后序遍历)。

💻 代码实现

package com.yao.tree;

/**
 * @Author: Yao
 * @Date: 2021/10/20 15:06
 */
public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {

        //测试
        HeroNode3 root = new HeroNode3(1, "tom");
        HeroNode3 node2 = new HeroNode3(3, "jack");
        HeroNode3 node3 = new HeroNode3(6, "mary");
        HeroNode3 node4 = new HeroNode3(8, "jon");
        HeroNode3 node5 = new HeroNode3(10, "mali");
        HeroNode3 node6 = new HeroNode3(14, "hh");

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree(root);
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        //进行后序线索化
        System.out.println("-----------------进行后序线索化----------------");
        threadedBinaryTree.threadedBinaryTreePost(root);
        //测试 10 节点
        System.out.println(node5.getLeft()+" --> "+node5.getLeftType());
        System.out.println(node5.getRight()+" --> "+node5.getRightType());

    }
}

/*
ThreadedBinaryTree 线索化二叉树
 */
class ThreadedBinaryTree{

    private HeroNode3 root;

    //为了实现线索化二叉树,需要创建一个指向当前节点的前驱结点的指针
    private HeroNode3 per;

    public ThreadedBinaryTree(HeroNode3 root) {
        this.root = root;
    }
    
    /**
     * 后序线索化二叉树
     * @param node 需要线索化的节点
     */
    public void threadedBinaryTreePost(HeroNode3 node){
        if (node == null){
            return;
        }
        //线索化左子树
        threadedBinaryTreePost(node.getLeft());
        //线索化右子树
        threadedBinaryTreePost(node.getRight());
        //线索化当前节点

        if (node.getLeft() == null){
            node.setLeft(per);
            node.setLeftType(1);
        }

        if (per != null && per.getRight() == null){
            per.setRight(node);
            per.setRightType(1);
        }
        per = node;
    }

}

//HeroNode3
class HeroNode3{

    private int no;

    private String name;

    private HeroNode3 left;

    private HeroNode3 right;

    //说明 如果 leftType == 0 表示leftType指向的是左子树,leftType == 1,表示指向的是 前驱结点
    private int leftType;
    //说明 如果 rightType == 0 表示rightType指向的是右子树,rightType == 0,表示指向的是 后继节点
    private int rightType;

    public HeroNode3(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 HeroNode3 getLeft() {
        return left;
    }

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

    public HeroNode3 getRight() {
        return right;
    }

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

    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;
    }

    @Override
    public String toString() {
        return "HeroNode3{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

4.2 后序线索化二叉树遍历实现
  • 遍历的起点:根节点左子树最左边的节点。该节点不一定是第一个打印数据的节点,该节点可能存在右子树,存在右子树要遍历右子树。

  • 后继:一直遍历该节点的后继节点,没有后继节点就判断是不是根节点(根节点没有右子树时就会出现这种情况)。如果不是根节点,那就找到节点的父亲,一直到找到根节点,继续判断根节点是不是存在右子树。

思路分析:

在这里插入图片描述
在这里插入图片描述
💻 代码实现

package com.yao.tree;

/**
 * @Author: Yao
 * @Date: 2021/10/20 15:06
 */
public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {

        //测试
        HeroNode3 root = new HeroNode3(1, "tom");
        HeroNode3 node2 = new HeroNode3(3, "jack");
        HeroNode3 node3 = new HeroNode3(6, "mary");
        HeroNode3 node4 = new HeroNode3(8, "jon");
        HeroNode3 node5 = new HeroNode3(10, "mali");
        HeroNode3 node6 = new HeroNode3(14, "hh");

        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree(root);
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node2.setParent(root);
        node3.setLeft(node6);
        node3.setParent(root);
        node4.setParent(node2);
        node5.setParent(node2);
        node6.setParent(node3);


        //进行后序线索化
        System.out.println("-----------------进行后序线索化----------------");
        threadedBinaryTree.threadedBinaryTreePost(root);
        //测试 10 节点
        System.out.println(node5.getLeft()+" --> "+node5.getLeftType());
        System.out.println(node5.getRight()+" --> "+node5.getRightType());
        System.out.println("后序线索化二叉树遍历.........");
        threadedBinaryTree.threadedBinaryTreePostList(root);

    }
}

/*
ThreadedBinaryTree 线索化二叉树
 */
class ThreadedBinaryTree{

    private HeroNode3 root;

    //为了实现线索化二叉树,需要创建一个指向当前节点的前驱结点的指针
    private HeroNode3 per;

    public ThreadedBinaryTree(HeroNode3 root) {
        this.root = root;
    }

    /**
     * 后序线索化二叉树
     * @param node 需要线索化的节点
     */
    public void threadedBinaryTreePost(HeroNode3 node){
        if (node == null){
            return;
        }
        //线索化左子树
        threadedBinaryTreePost(node.getLeft());
        //线索化右子树
        threadedBinaryTreePost(node.getRight());
        //线索化当前节点

        if (node.getLeft() == null){
            node.setLeft(per);
            node.setLeftType(1);
        }

        if (per != null && per.getRight() == null){
            per.setRight(node);
            per.setRightType(1);
        }
        per = node;
    }


    /**
     * 遍历后序线索化二叉树
     * @param node 根节点
     */
    public void threadedBinaryTreePostList(HeroNode3 node){

        if (node == null){
            return;
        }

        HeroNode3 temp = node;
        //定义一个辅助指针
        HeroNode3 pre = null;


        while (temp != null){

            //从根节点的左子树开始找到最左边的节点
            while (temp.getLeft() != null && temp.getLeftType() == 0){
                temp = temp.getLeft();
            }

            while (temp != null && temp.getRightType() == 1){  //最左节点含有后继节点
                System.out.println(temp);
                pre = temp;
                temp = temp.getRight();
            }

            if (temp == node){
                System.out.println(temp);
                return;
            }

            while (temp != null && temp.getRight() == pre){
                System.out.println(temp);
                pre = temp;
                temp = temp.getParent();
            }

            if (temp != null && temp.getRightType() == 0){
                temp = temp.getRight();
            }

        }
    }


}

//HeroNode3
class HeroNode3{

    private int no;

    private String name;

    //添加父节点
    private HeroNode3 parent;

    private HeroNode3 left;

    private HeroNode3 right;

    //说明 如果 leftType == 0 表示leftType指向的是左子树,leftType == 1,表示指向的是 前驱结点
    private int leftType;
    //说明 如果 rightType == 0 表示rightType指向的是右子树,rightType == 0,表示指向的是 后继节点
    private int rightType;

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

    public HeroNode3 getParent() {
        return parent;
    }

    public void setParent(HeroNode3 parent) {
        this.parent = parent;
    }

    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 HeroNode3 getLeft() {
        return left;
    }

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

    public HeroNode3 getRight() {
        return right;
    }

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

    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;
    }

    @Override
    public String toString() {
        return "HeroNode3{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

🎃 完结

  • 15
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_子栖_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值