线索化二叉树及其遍历(java代码实现)

线索化二叉树及其遍历(java代码实现)

线索化二叉树介绍

当一个二叉树存在多个叶子结点,这下叶子节点的左右子树没有利用上====>我们考虑将其充分利用,让各个节点可以指向自己的前后节点
1)n个节点的二叉树链表中含有(n+1)个空指针域
2)利用二叉树链表中的空指针域,存放该节点在某种遍历次序下的前驱节点和后继节点
3)这种加上了线索的二叉树链表称为线索链表,相应的二叉树称为线索二叉树(ThreadedBinaryTree)
4)根据线索化二叉树的遍历次序的不同:可分为前序线索二叉树,中序线索二叉树,后序线索二叉树
5)前驱节点:即一个节点在该遍历次序下的上一个节点
6)后继节点:即一个节点在该遍历次序下的下一个节点
以下代码以中序遍历次序为例:二叉树的中序遍历结果为:{8,3,10,1,14,6}
实现线索化该二叉树
注意:
1.前序线索二叉树/后序线索二叉树只需要修改threadedNode中进行线索化步骤次序即可{前序为:2->1->3;后序为:1->3->2}
2.线索化二叉树以后,因为Node节点现在有些可能左指针为左子树,也有可能为前驱节点;右指针可能为右子树,也可能为后驱节点,所以我们需要在Node节点类中添加leftType和rightType属性分别记录左右指针的类型
package com.bingym.tree.threadedbinarytree;

public class ThreadedBinaryTreeDemo {
    /*
    * 线索化二叉树:
    * 当一个二叉树存在多个叶子结点,这下叶子节点的左右子树没有利用上====>我们考虑将其充分利用,让各个节点可以指向自己的前后节点
    * 1)n个节点的二叉树链表中含有(n+1)个空指针域
    * 2)利用二叉树链表中的空指针域,存放该节点在某种遍历次序下的前驱节点和后继节点
    * 3)这种加上了线索的二叉树链表称为线索链表,相应的二叉树称为线索二叉树(ThreadedBinaryTree)
    * 4)根据线索化二叉树的遍历次序的不同:可分为前序线索二叉树,中序线索二叉树,后序线索二叉树
    * 5)前驱节点:即一个节点在该遍历次序下的上一个节点
    * 6)后继节点:即一个节点在该遍历次序下的下一个节点
    * 以下代码以中序遍历次序为例:二叉树的中序遍历结果为:{8,3,10,1,14,6}
    * 实现线索化该二叉树
    * 注意:
    * 1.前序线索二叉树/后序线索二叉树只需要修改threadedNode中进行线索化步骤次序即可{前序为:2->1->3;后序为:1->3->2}
    * 2.线索化二叉树以后,因为Node节点现在有些可能左指针为左子树,也有可能为前驱节点;右指针可能为右子树,也可能为后驱节点,所以我们需要在Node节点类中添加leftType和rightType属性分别记录左右指针的类型
    *
    * */
    public static void main(String[] args) {
        Node node1 = new Node(1,"node1");
        Node node2 = new Node(3,"node2");
        Node node3 = new Node(6,"node3");
        Node node4 = new Node(8,"node4");
        Node node5 = new Node(10,"node5");
        Node node6 = new Node(14,"node6");
        node1.left = node2;
        node1.right = node3;
        node2.left = node4;
        node2.right = node5;
        node3.left = node6;
        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.root = node1;//设置根节点
        //原二叉树的中序遍历为
        System.out.println("原二叉树的中序遍历为:");
        threadedBinaryTree.infixOrder();
        threadedBinaryTree.threadedNode();//进行中序线索化

        //测试: 以10号节点测试
        //Node leftNode = node5.left;
        //Node rightNode = node5.right;
        //System.out.println("10号结点的前驱结点是 ="  + leftNode); //3
        //System.out.println("10号结点的后继结点是="  + rightNode); //1

        //测试:线索化二叉树(此处为中序线索化二叉树)的遍历
        System.out.println("线索化二叉树(此处为中序线索化二叉树)的遍历为:");
        threadedBinaryTree.threadedList();
    }
}


//定义中序线索二叉树
class ThreadedBinaryTree {
    public Node root;//root节点
    //为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
    //在递归进行线索化时,pre 总是保留前一个结点
    public Node pre = null;//初始化pre为null

    //重载threadedNode
    public void threadedNode() {
        this.threadedNode(root);
    }

    //遍历线索化二叉树(此处为中序线索化二叉树的遍历方法)
    public void threadedList() {
        //首先定义一个辅助变量指向root
        Node node = root;
        //进行循环遍历
        while (node != null) {  //当node节点不为空时,一直遍历
            //循环找到node的leftType == 1的节点:说明其为最左边的节点(满足中序遍历的要求)
            while (node.leftType == 0) {
                node = node.left;
            }
            //找到即为最左侧的值
            System.out.println(node);
            //此时由于我们遍历的二叉树是已经进行中序排序的线索化二叉树,每个rightType == 1的节点的right即为下一个中序遍历的节点
            //所以我们循环遍历,如果当前节点指向后继节点(即node.rightType == 1),则一直输出
            while (node.rightType == 1) {
                node = node.right;
                System.out.println(node);
            }
            //最后退出循环,我们更新下一轮遍历的node节点
            node = node.right;
        }
    }

    //实现将该二叉树进行线索化的方法
    public void threadedNode(Node node) {
        //首先判断该节点是否为空,若为空,则无法进行线索化(为空,不存在前驱和后继,故无法进行线索化)
        if (node == null) {
            return;
        }
        //中序线索二叉树
        //1.先线索化左子树
        threadedNode(node.left);
        //2.再线索化当前节点(核心代码)
        //线索化当前节点(主要是分别线索化其左右子节点)
        //2.1处理前驱节点
        if (node.left == null) {    //若左子树为空
            //将当前节点的左指针指向前驱节点pre
            node.left = pre;
            //同时修改当前节点左指针的类型
            node.leftType = 1;//说明此时为前驱节点
        }
        //2.2处理后继节点
        if (pre != null && pre.right == null) { //如果该节点的前驱节点的右指针为空(并且pre存在)
            //则将前驱节点的右指针指向当前节点====完成上一个节点的后继节点工作,当前节点的后继节点工作需要在下一个节点中完成
            pre.right = node;
            pre.rightType = 1;//将前驱节点的右指针类型置为1
        }

        //2.3!!!!重点:在每一次处理完一个节点以后,需要将当前节点作为下一个节点的前驱节点
        pre = node;

        //3.最后线索化右子树
        threadedNode(node.right);
    }

    //前序遍历
    public void preOrder() {
        if (this.root != null) {
            this.root.preOrder();
        }else {
            System.out.println("二叉树为空,无法进行前序遍历...");
        }
    }

    //中序遍历
    public void infixOrder() {
        if (this.root != null) {
            this.root.infixOrder();
        }else {
            System.out.println("二叉树为空,无法进行中序遍历...");
        }
    }

    //后序遍历
    public void postOrder() {
        if (this.root != null) {
            this.root.postOrder();
        }else {
            System.out.println("二叉树为空,无法进行后序遍历...");
        }
    }

    //前序遍历查找方法
    public Node preOrderSearch(int id) {
        if (this.root != null) {
            return this.root.preOrderSearch(id);
        }else {
            return null;
        }
    }

    //中序遍历查找方法
    public Node infixOrderSearch(int id) {
        if (this.root != null) {
            return this.root.infixOrderSearch(id);
        }else {
            return null;
        }
    }

    //后序遍历查找方法
    public Node postOrderSearch(int id) {
        if (this.root != null) {
            return this.root.postOrderSearch(id);
        }else {
            return null;
        }
    }

    //删除节点的方法
    public void deleteNode(int id) {
        //首先需要判断二叉树是否为空树或者只有一个root节点
        if (this.root != null) {
            //如果只有一个root结点, 这里立即判断root是不是就是要删除结点
            if (this.root.id == id) {
                this.root = null;
            }else {
                //调用deleteNode方法
                this.root.deleteNode(id);
            }
        }else {
            //该二叉树为空
            System.out.println("该二叉树为空树,不能删除");
        }
    }
}

//节点类
class Node {
    public int id;
    public String name;
    public Node left;//默认为null
    public Node right;//,默认为null

    //在原有二叉树的基础上定义两个属性,区分该二叉树的左右子节点是左右子树节点,还是前驱节点,后继节点(0:表示为左右子树节点.1:表示为前驱节点,后继节点)
    public int leftType;//初始化为0
    public int rightType;//初始化为0

    //构造器
    public Node(int id, String name) {
        this.id = id;
        this.name = name;
    }

    //toString方法:只打印id和name信息
    @Override
    public String toString() {
        return "Node{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    //前序遍历方法preOrder()
    public void preOrder() {
        //首先输出当前节点
        System.out.println(this);
        //如果当前节点的左子节点不为空,则递归继续进行前序遍历
        if (this.left != null) {
            this.left.preOrder();
        }
        //如果当前节点的右子节点不为空,则递归继续进行前序遍历
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    //中序遍历方法infixOrder()
    public void infixOrder() {
        //如果当前节点的左子节点不为空,则递归继续进行中序遍历
        if (this.left != null) {
            this.left.infixOrder();
        }
        //输出当前节点
        System.out.println(this);
        //如果当前节点的右子节点不为空,则递归继续进行中序遍历
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    //后序遍历方法:postOrder()
    public void postOrder() {
        //如果当前节点的左子节点不为空,则递归继续进行后序遍历
        if (this.left != null) {
            this.left.postOrder();
        }
        //如果当前节点的右子节点不为空,则递归继续进行后序遍历
        if (this.right != null) {
            this.right.postOrder();
        }
        //最后输出当前节点
        System.out.println(this);
    }

    //前序遍历查找方法:preOrderSearch(int id)
    /*
     * 前序遍历查找思路:
     * 1.先与当前节点(初始为root节点)的id进行比较:是否等于要查找的节点的id值
     * 2.如果相等,则直接返回当前节点
     * 3.如果不相等,则判断左子节点是否为空
     * 4.如果不为空:则进行递归前序查找
     * 5.如果左递归前序查找找到了结果,则直接返回,否则对右子节点进行相同操作
     * */
    public Node preOrderSearch(int id) {
        System.out.println("递归preOrderSearch");
        //1.与当前节点进行比较,如果是,则直接返回当前节点
        if (this.id == id) {
            return this;
        }
        //2.判断当前节点的左节点是否为空
        //定义返回结果resNode
        Node resNode = null;//初始化为null
        if (this.left != null) {
            //3.若不为空,则进行左递归前序遍历查找,判断返回结果
            resNode = this.left.preOrderSearch(id);
        }
        //4.若找到则返回结果
        if (resNode != null) {
            return resNode;
        }
        //5.若未找到,则判断当前节点的右子节点是否为空
        if (this.right != null) {
            resNode = this.right.preOrderSearch(id);
        }
        //直接返回查找的结果
        return resNode;
    }

    //中序遍历查找方法:infixOrderSearch(int id)
    /*
     * 中序遍历查找思路:
     * 1.判断左子节点是否为空
     * 2.如果不为空:则进行递归前序查找
     * 3.如果左递归前序查找找到了结果,则直接返回,否则与当前节点(初始为root节点)的id进行比较:是否等于要查找的节点的id值
     * 4.如果相等,则直接返回当前节点,如果不相等,则对右子节点进行相同操作
     * */
    public Node infixOrderSearch(int id) {
        Node resNode = null;
        //1.首先判断当前节点的左子节点是否为空
        if (this.left != null) {
            resNode = this.left.infixOrderSearch(id);
        }
        //2.若找到,直接返回结果
        if (resNode != null) {
            return resNode;
        }
        System.out.println("递归infixOrderSearch");
        //3.若未找到,则语当前节点进行比较
        if (this.id == id) {
            return this;
        }
        //4.若仍未找到,则判断当前节点的右子节点是否为空
        if (this.right != null) {
            resNode = this.right.infixOrderSearch(id);
        }
        //直接返回结果
        return resNode;
    }

    //后序遍历查找方法:postOrderSearch(int id)
    /*
     * 思路:
     * 1.先判断当前节点的左子节点是否为空:
     * 2.再判断当前节点的右子节点是否为空:
     * 3.最后与当前节点进行比较,最后返回其结果
     *
     * */
    public Node postOrderSearch(int id) {
        Node resNode = null;
        //1.首先判断当前节点的左子节点是否为空
        if (this.left != null) {
            resNode = this.left.postOrderSearch(id);
        }
        //2.若找到,直接返回结果
        if (resNode != null) {
            return resNode;
        }
        //3.若未找到,则继续判断当前节点的右子节点是否为空
        if (this.right != null) {
            resNode = this.right.postOrderSearch(id);
        }
        //4.若找到,则直接返回结果
        if (resNode != null) {
            return resNode;
        }
        System.out.println("递归postOrderSearch");
        //5.若仍未找到,则域当前节点进行比较
        if (this.id == id) {
            return this;
        }
        //返回结果
        return resNode;
    }

    //删除节点的方法deleteNode(int id)
    /*
     * 思路:首先我们需要判断该二叉树是空时(root = null)或者只有一个节点root,并且root节点即为要删除的节点时,则直接将二叉树置空
     * 然后再进行以下步骤:
     * 1.由于二叉树的单向的:所以我们判断时:选择判断当前节点的左右子节点是否为需要删除的节点,而不是直接判断当前节点
     * 2.如果当前节点的左子节点不为空,并且左子节点即为需要删除的节点,就将this.left = null,并且返回(结束递归)
     * 3.如果当前节点的右子节点不为空,并且右子节点即为需要删除的节点,就将this.right = null,并且返回(结束递归)
     * 4.如果第2,3步骤无法删除:那么选择先进行左子树递归删除;若仍未删除,再选择进行右子树递归删除
     * */
    public void deleteNode(int id) {
        if (this.left != null && this.left.id == id) {
            this.left = null;
            return;
        }
        if (this.right != null && this.right.id == id) {
            this.right = null;
            return;
        }
        //向左子树进行递归删除
        if(this.left != null) {
            this.left.deleteNode(id);
        }
        //向右子树进行递归删除
        if(this.right != null) {
            this.right.deleteNode(id);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值