线索化二叉树

基本说明

1、二叉链表中,n个节点,含有2n个指针;除根节点外,每个节点都要一个指针引用,总共需要n-1个指针;含2n-(n-1)=n+1个空指针域

2、利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针,这种附加的指针称为"线索"

(1)二叉树进行遍历,遍历后的顺序;前驱节点:一个节点的前一个节点;后继节点:一个节点的后一个节点;前序、中序、后序遍历产生的前驱节点、后续节点可能不同

(2)这种加上了线索的二叉链表称为线索链表, 相应的二叉树称为线索二叉树

(3)根据线索性质的不同, 线索二叉树可分为前序线索二叉树、中序线索二叉树、后序线索二叉树

3、节点的left可能指向左子树,也可能指向前驱节点;节点的right可能指向右子树,也可能指向后续节点

4、前驱节点:一个节点的前一个节点

5、后继节点:一个节点的后一个节点

 

一棵左右子树不空的二叉树在线索化后,其空指针域数

1、前序线索化,空指针数 = 1,位置:在最右下叶子的右指针

2、中序线索化,空指针数 = 2,位置:在最左下叶子的左指针和最右下叶子的右指针

3、后续线索化,空指针数 = 1,位置:在最左下叶子的左指针

 

线索二叉树的遍历

1、线索化后,各个结点指向有变化,原来的遍历方式不能使用,需要使用新的方式遍历线索化二叉树,各个节点可以通过线性方式遍历,无需使用递归方式,提高了遍历的效率,哪种线索化就对应哪种遍历次序

2、实现思路(中序遍历)

(1)定义一个指针,初始指向root,找最深层的具有前驱结点的左节点,并打印

(2)沿着左节点遍历打印其后继节点,直到没有后继节点,指针指向当前步骤最后一个后继节点

(3)以指针指向其右节点作为新的起始节点,继续找当前步骤最深层的具有前驱节点的左节点

(4)直到最后得到的右节点为 null

 

二叉树在线索化后

1、仍不能有效求解的问题

(1)先序线索二叉树找先序前驱

(2)后序线索二叉树找后序后继

2、原因

(1)前序遍历(根左右),最先访问的子树的根节点,子树根节点的两个指针域都指向子树了,所以不能空出来存放前驱线索;最后访问的节点都有空的右指针域,以指向后继

(2)中序遍历(左根右),最先访问的节点都有空的左指针域,以指向前驱;最后访问的节点都有空的右指针域,以指向后继

(3)后续遍历(左右根),最先访问的节点都有空的左指针域,以指向前驱;最后访问的子树的根节点,子树根节点的两个指针域都指向子树了,所以不能空出来存放后继线索

 

代码实现

public class ThreadBinaryTree {
    public Node root;
    public Node pre = null;//创建一个指向当前节点的前驱节点的指针,在递归进行线索化时,pre总是保留前一个节点

    //重载postThread方法
    public void postThread() {
        this.postThread(root);
    }

    //后序线索化
    public void postThread(Node node) {
        if (node == null) {//node == null,不能线索化
            return;
        }
        if (node.left != null) {
            node.left.parent = node;
        }
        if (node.right != null) {
            node.right.parent = node;
        }
        postThread(node.left);//向左递归线索化左子树
        postThread(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;//每处理一个节点后,让当前节点是下一个结点的前驱节点
    }

    //遍历后序线索化二叉树
    public void postList() {
        Node node = root;//定义一个变量,存储当前遍历的结点,从root开始
        while (node != null && node.leftType == 0) {//从整棵树最深处的左节点开始
            node = node.left;
        }
        while (node != null) {
            if (node.leftType == 1) {
                System.out.println(node);
                pre = node;
                node = node.right;
            } else if (node.right == pre) {//若上个处理的节点是当前节点的右节点,说明node是该子树的根节点
                System.out.println(node);//打印该子树的根节点
                if (node == root) {//检查遍历完成的是子树还是整棵树
                    return;
                }
                pre = node;//pre指向该子树的根节点
                node = node.parent;//node指向其父节点
            } else {//node所在的子树遍历完成,即其父节点左边子树(不包括父节点)
                node = node.right;//父节点的右边子树的根节点
                while (node != null && node.leftType == 0) {//找右边子树最深层的左节点
                    node = node.left;
                }
            }
        }
    }

    //重载preThread方法
    public void preThread() {
        this.preThread(root);
    }

    //前序线索化
    public void preThread(Node node) {
        if (node == null) {//node == null,不能线索化
            return;
        }
        //线索化当前节点
        if (node.left == null) {//处理前驱节点
            node.left = pre;//让当前节点的左指针指向前驱节点
            node.leftType = 1;//修改当前节点的左指针的类型
        }
        if (pre != null && pre.right == null) {//处理后继节点
            pre.right = node;//让前驱节点的右指针指向当前节点
            pre.rightType = 1;//修改前驱节点的右指针类型
        }
        pre = node;//每处理一个节点后,让当前节点是下一个结点的前驱节点
        //只有指向子树才进入,否则死循环
        if (node.leftType == 0) {
            preThread(node.left);//向左递归线索化左子树
        }
        if (node.rightType == 0) {
            preThread(node.right);//向右递归线索化右子树
        }
    }

    //遍历前序线索化二叉树
    public void preList() {
        Node node = root;//定义一个变量,存储当前遍历的结点,从root开始
        //存在左子节点就往左走,否则往右走,此时右指针一定是前序遍历的下一个节点
        while (node != null) {
            System.out.println(node);
            if (node.leftType == 0) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
    }

    //重载infixThread方法
    public void infixThread() {
        this.infixThread(root);
    }

    //中序线索化
    public void infixThread(Node node) {
        if (node == null) {//node == null,不能线索化
            return;
        }
        infixThread(node.left);//向左递归线索化左子树
        //线索化当前节点
        if (node.left == null) {//处理前驱节点
            node.left = pre;//让当前节点的左指针指向前驱节点
            node.leftType = 1;//修改当前节点的左指针的类型
        }
        if (pre != null && pre.right == null) {//处理后继节点
            pre.right = node;//让前驱节点的右指针指向当前节点
            pre.rightType = 1;//修改前驱节点的右指针类型
        }
        pre = node;//每处理一个节点后,让当前节点是下一个结点的前驱节点
        infixThread(node.right);//向右递归线索化右子树
    }

    //遍历中序线索化二叉树
    public void infixList() {
        Node node = root;//定义一个变量,存储当前遍历的结点,从root开始
        while (node != null) {
            while (node.leftType == 0) {//找最深层的左节点
                node = node.left;
            }
            System.out.println(node);
            while (node.rightType == 1) {//若当前节点有后继节点
                node = node.right;//node指向该子树的根节点
                System.out.println(node);//打印该(子)树的根节点
            }//退出循环时,node的左边子树遍历完成(包括node)
            node = node.right;//node指向其右节点作为新的起始节点,即遍历node的右边子树
        }
    }
}

class Node {
    public int no;
    public String name;//视作data
    public Node left;//默认null
    public Node right;//默认null
    public Node parent;//后序线索化及遍历使用,指针指向父节点
    public int leftType;//等于0,指向左子树;等于1,指向前驱节点
    public int rightType;//等于0,指向右子树;等于1,指向后续节点

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

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

 

posted @   半条咸鱼  阅读(182)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示