线索化二叉树的创建和遍历

每当诞生新的数据结构时,一定要联系它对应的某一类问题。新的算法思路,不仅仅是解决问题,还要优化问题,提高程序效率,减少程序占用空间。

下面,我们来引入一个问题,结合问题,体会什么是线索化二叉树。

将数组 arr = {1,3,6,8,10,14} 构建成一颗二叉树时,结合下图,我们可以发现6,8,10,14这几个结点的左右指针,并没有完全的利用上,造成了一定空间的浪费,为了解决这个问题,我们该怎么办呢?这时候就引入我们的线索二叉树。

线索二叉树是指利用二叉链表中某些结点的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针,解决了二叉树是单向而子结点找不到父结点的问题。其中附加的指针称为线索,对应的二叉树称为线索二叉树。

上面讲到了某种遍历次序,我们这里的遍历是指前序、中序、后序遍历,因此相应的二叉树叫做,前序线索二叉树、中序线索二叉树、后序线索二叉树。

这里有个结点个数的小规律:n个结点的二叉链表中含有n+1个空指针域。一个结点2个指针,n个结点需要n-1个指针相连(把二叉树当成线性的),剩下的指针就是空指针域:2n - (n-1) = n+1;

现在主要的问题就是怎么实现空指针指向某个结点的前驱和后继啦。我们以某个结点为例,依次类推。

根据中序遍历的结果:8,3,10,1,14,6。我们会注意到一个问题,在根节点1的位置,它的前序结点指向了左子树3,后继结点指向了右子树6,并没有指向10和14。因此,要注意node结点的left指向的可能是左子树也可能是左结点,right指向的可能是右子树也可能是右结点

实现二叉树转为中序线索二叉树的思路:左--->父--->中

  1. 结合上图,创建指向当前结点的前驱结点的指针pre(创建pre是为了能够让当前结点找到它的前驱是谁,因为二叉树是单向的,子结点并不能够找到父结点是谁
  2. 中序遍历,先递归线索化左子树,找到左子树结点,
  3. 找到后,就要处理当前结点的前驱结点,处理规则是判断它的left是否为null,如果为null,left指向前驱pre,
  4. 接着处理后继结点,处理规则是判断它的right是否为null,如果为null,right指向后继结点。
class HeroNode {
    private int no;
    private String name;
    private HeroNode left; // 左结点
    private HeroNode right; // 右结点
    private int leftType; // 0:指向左子树,1指向前驱结点
    private int rightType; // 0:指向右子树,1指向后继结点

    public HeroNode(int no, String name) {
        this.no = no;
        this.name = name;
    }
    // 省略getter、setter方法
}

将HeroNode转换成线索化二叉树:

class ThreadedBinaryTree {
    private HeroNode pre = null;
    
    # 完整代码在最后给出
    
    public void threadedNodes(HeroNode node) {
        if (node == null) {
            return;
        }
        // 左子树线索化递归
        threadedNodes(node.getLeft());
        // 处理当前结点的前驱结点
        if (node.getLeft() == null) {
            node.setLeft(pre); // 第一次是8,指向自身
            node.setLeftType(1); // 1表示指向前驱结点,0表示指向左子树
        }
        // 处理当前结点的的后继结点
        if (pre != null && pre.getRight() == null) {
            pre.setRight(node);
            pre.setRightType(1);// 1表示指向前驱结点,0表示指向左子树
        }
        pre = node; // !!!一定要让下一个结点的前驱结点是当前结点
        // 右子树线索化递归
        threadedNodes(node.getRight()); // 8处理完后,根据栈内存原理就开始处理3
    }
}

实现中序线索二叉树遍历思路:

  1. 定义一个变量指向根节点,存储当前遍历的结点,
  2. 循环遍历,根据leftType的值来判断,当前结点是指向子树还是结点,1指向结点,0指向子树,
  3. 按照上面规则一直遍历即可。
class ThreadedBinaryTree {
    private HeroNode root;

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

    // 遍历线索化二叉树
    public void threadedList(){
        if (root == null){
            return;
        }
        HeroNode node = root;
        while (node != null){
            while (node.getLeftType() == 0){ //指向左子树
                node = node.getLeft(); // 直到找到左结点
            }
            // 找到当前结点后打印前序结点
            System.out.print(node.getNo()+" ");
            // 判断当前结点的右指针是否指向后继结点
            while (node.getRightType() == 1){
                node = node.getRight();
                System.out.print(node.getNo()+" "); // 打印后继结点
            }
            node = node.getRight(); //移动到下一个结点,后序结点就是下一个结点
        }
    }
}

完整代码:

package cn.cwj.threadBinaryTree;

public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {
        // 手动创建图示结点
        HeroNode root = new HeroNode(1, "宋江"); // 根结点
        HeroNode heroNode2 = new HeroNode(3, "吴用"); // 根节点的左子结点
        HeroNode heroNode3 = new HeroNode(6, "武松"); // 根节点的右子结点
        HeroNode heroNode4 = new HeroNode(8, "林冲"); // 根节点的右子结点的右子结点
        HeroNode heroNode5 = new HeroNode(10, "李逵"); // 根节点的右子结点的右子结点
        HeroNode heroNode6 = new HeroNode(14, "晁盖"); // 根节点的右子结点的右子结点

        // 手动创建图示二叉树
        root.setLeft(heroNode2);
        root.setRight(heroNode3);
        heroNode2.setLeft(heroNode4);
        heroNode2.setRight(heroNode5);
        heroNode3.setLeft(heroNode6);

        // 测试
        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree(root);
        threadedBinaryTree.threadedNodes();

        // 测试10号结点的前驱和后序
        HeroNode left = heroNode5.getLeft();
        HeroNode right = heroNode5.getRight();
        System.out.println(heroNode5.getNo() + "的前驱结点:" + left.getNo());
        System.out.println(heroNode5.getNo() + "的后继结点:" + right.getNo());

        //线索二叉树的遍历
        System.out.println("线索二叉树的遍历:");
        threadedBinaryTree.threadedList();
    }
}


class ThreadedBinaryTree {
    private HeroNode root;
    private HeroNode pre = null;

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

    // 重载,减少传入参数
    public void threadedNodes() {
        this.threadedNodes(root);
    }

    public void threadedNodes(HeroNode node) {
        if (node == null) {
            return;
        }
        // 左子树线索化递归
        threadedNodes(node.getLeft());
        // 处理当前结点的前驱结点
        if (node.getLeft() == null) {
            node.setLeft(pre); // 第一次是8,指向自身
            node.setLeftType(1); // 1表示指向前驱结点,0表示指向左子树
        }
        // 处理当前结点的的后继结点
        if (pre != null && pre.getRight() == null) {
            pre.setRight(node);
            pre.setRightType(1);// 1表示指向前驱结点,0表示指向左子树
        }
        pre = node; // !!!一定要让下一个结点的前驱结点是当前结点
        // 右子树线索化递归
        threadedNodes(node.getRight()); // 8处理完后,根据栈内存原理就开始处理3
    }

    // 遍历线索化二叉树
    public void threadedList() {
        if (root == null) {
            return;
        }
        HeroNode node = root;
        while (node != null) {
            while (node.getLeftType() == 0) { //指向左子树
                node = node.getLeft(); // 直到找到左结点
            }
            // 找到当前结点后打印前序结点
            System.out.print(node.getNo() + " ");
            // 判断当前结点的右指针是否指向后继结点
            while (node.getRightType() == 1) {
                node = node.getRight();
                System.out.print(node.getNo() + " "); // 打印后继结点
            }
            node = node.getRight(); //移动到下一个结点,后序结点就是下一个结点
        }
    }
}


class HeroNode {
    private int no;
    private String name;
    private HeroNode left; // 左结点
    private HeroNode right; // 右结点
    private int leftType; // 0:指向左子树,1指向前驱结点
    private int rightType; // 0:指向右子树,1指向后继结点

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

    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 "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值