java实现线索化二叉树的前序、中序、后续的遍历(完整代码)

java实现线索化二叉树的前序、中序、后续的遍历

比如创建一个二叉树

           1
       /             3        6
     / \         /
    8  10  14
  • 线索化二叉树几个概念

    • n个节点的二叉链表中含有n+1
      【公式2n-(n-1)=n+1】个空指针域。利用二叉链表中的空指针域,存放指向该节点在某种遍历次序下的前驱和后继节点的指针(这种附加指针成为线索)。
      如下面的就是6+1=7个空指针域 (8,10,14各有连个指针没有指向 6有一个)
    • 加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。分为前序线索二叉树、中序线索二叉树、后序线索二叉树
    • 一个节点的前一个节点,称为前驱节点
    • 一个节点的后一个节点,称为后继节点
    • 线索化后的二叉树,节点可能指向的是前驱或者后继节点,也有可能指向的是本身的二叉树的节点
  • 前序、中序、后序线索化和遍历
//创建树的节点
/**
 * 〈节点定义〉
 *
 * @author nick
 * @create 2019/9/17
 * @since 1.0.0
 */
@Data
public class HeroNode {
    private int no;
    private String name;
    /**
     * //默认null
     */
    private HeroNode left;
    /**
     * //默认null
     */
    private HeroNode right;

    /**
     * //父节点的指针(为了后序线索化使用)
     */
    private HeroNode parent;

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

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

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

}
/**
 * 〈线索化二叉树〉
 * 1
 * /    * 3     6
 * / \   /
 * 8  10 14
 *
 * @author nick
 * @create 2019/9/17
 * @since 1.0.0
 */
public class ThreadedBinaryTree {
    private HeroNode root;

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

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

    /**
     * 重载一把threadedNodes方法
     */
    public void threadedNodes() {
        this.threadedNodes(root);
    }

    /**
     * 重载一把threadedNodesPre方法
     */
    public void threadedNodesPre() {
        this.threadedNodesPre(root);
    }

    /**
     * 重载一把threadedNodesAfter方法
     */
    public void threadedNodesAfter() {
        this.threadedNodesAfter(root);
    }

    /***********************遍历线索化二叉树开始**********************/

    /**
     * 中序遍历线索化二叉树的方法
     * <p>
     */

    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();

        }
    }

    /**
     * 前序线索化二叉树遍历方法
     * 1
     * /        * 3     6
     * / \   /
     * 8  10 14
     * <p>
     * {1,3,8,10,6,14}
     */
    public void threadedListPre() {
        //定义一个变量,存储当前遍历的结点,从root开始
        HeroNode node = root;
        while ( node != null ) {
            while ( node.getLeftType() == 0 ) {
                //如果是叶子节点,非前驱节点,打印当前这个结点
                System.out.print(node + ",");
                node = node.getLeft();
            }
            System.out.print(node + ",");
            //替换这个遍历的结点
            node = node.getRight();
        }
    }

    /**
     * 后序线索化二叉树遍历方法
     * <p>
     * 注意后序有点复杂,需要建立二叉树的时候,将节点的parent进行赋值,否则不能遍历成功
     * 1
     * /        * 3     6
     * / \   /
     * 8  10 14
     * <p>
     * {8,10,3,1,14,6}
     * 1. 如果leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点
     * 2. 如果rightType == 0 表示指向是右子树, 如果 1表示指向后继结点
     */
    public void threadedListAfter() {
        //1、找后序遍历方式开始的节点
        HeroNode node = root;
        while ( node != null && node.getLeftType() == 0 ) {
            node = node.getLeft();
        }
        while ( node != null ) {
            //右节点是线索
            if (node.getRightType() == 1) {
                System.out.print(node + ", ");
                pre = node;
                node = node.getRight();
            } else {
                //如果上个处理的节点是当前节点的右节点
                if (node.getRight() == pre) {
                    System.out.print(node + ", ");
                    if (node == root) {
                        return;
                    }
                    pre = node;
                    node = node.getParent();
                } else {    //如果从左节点的进入则找到有子树的最左节点
                    node = node.getRight();
                    while ( node != null && node.getLeftType() == 0 ) {
                        node = node.getLeft();
                    }
                }
            }
        }

    }

    /***********************遍历线索化二叉树结束**********************/

    /****************线索化二叉树开始********************************/

    /**
     * 中序线索化
     * 得到的数组{8, 3, 10, 1, 14, 6}
     * 1
     * /        * 3     6
     * / \   /
     * 8  10 14
     *
     * @param node 就是当前需要线索化的结点
     */
    public void threadedNodes(HeroNode node) {
        //如果node==null, 不能线索化
        if (node == null) {
            return;
        }
        //(一)先线索化左子树
        threadedNodes(node.getLeft());
        //(二)线索化当前结点[有难度]
        //处理当前结点的前驱结点
        //以8结点来理解
        //8结点的.left = null , 8结点的.leftType = 1
        if (null == node.getLeft()) {
            //让当前结点的左指针指向前驱结点
            node.setLeft(pre);
            //修改当前结点的左指针的类型,指向前驱结点
            node.setLeftType(1);
        }
        //处理后继结点,是下一次进行处理,有点不好理解
        if (pre != null && pre.getRight() == null) {
            //让前驱结点的右指针指向当前结点
            pre.setRight(node);
            //修改前驱结点的右指针类型
            pre.setRightType(1);
        }
        //!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
        pre = node;
        //(三)在线索化右子树
        threadedNodes(node.getRight());
    }

    /**
     * 前序线索化
     * 变成数组后{1,3,8,10,6,14}
     * 1
     * /        * 3     6
     * / \   /
     * 8  10 14
     *
     * @param node 就是当前需要线索化的结点
     */
    public void threadedNodesPre(HeroNode node) {
        //如果node==null, 不能线索化
        if (node == null) {
            return;
        }
        //左指针为空,将左指针指向前驱节点
        //8结点的.left = 上一个节点 , 8结点的.leftType = 1
        if (node.getLeft() == null) {
            //让当前结点的左指针指向前驱结点
            node.setLeft(pre);
            //修改当前结点的左指针的类型,指向前驱结点
            node.setLeftType(1);
        }
        //处理后继结点,是下一次进行处理,有点不好理解
        if (pre != null && pre.getRight() == null) {
            //让前驱结点的右指针指向当前结点
            pre.setRight(node);
            //修改前驱结点的右指针类型
            pre.setRightType(1);
        }
        //!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
        pre = node;
        //(一)先线索化左子树
        if (node.getLeftType() != 1) {
            threadedNodesPre(node.getLeft());
        }
        //(三)再线索化右子树
        if (node.getRightType() != 1) {
            threadedNodesPre(node.getRight());
        }

    }

    /**
     * 后序线索化
     * 变成数组后{8,10,3,1,14,6}
     *
     * @param node
     */
    public void threadedNodesAfter(HeroNode node) {
        //如果node==null, 不能线索化
        if (node == null) {
            return;
        }

        //(一)先线索化左子树
        threadedNodesAfter(node.getLeft());
        //(三)再线索化右子树
        threadedNodesAfter(node.getRight());

        //左指针为空,将左指针指向前驱节点
        //8结点的.left = 上一个节点 , 8结点的.leftType = 1
        if (node.getLeft() == null) {
            //让当前结点的左指针指向前驱结点
            node.setLeft(pre);
            //修改当前结点的左指针的类型,指向前驱结点
            node.setLeftType(1);
        }
        //处理后继结点,是下一次进行处理,有点不好理解
        if (pre != null && pre.getRight() == null) {
            //让前驱结点的右指针指向当前结点
            pre.setRight(node);
            //修改前驱结点的右指针类型
            pre.setRightType(1);
        }
        //!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
        pre = node;
    }

    /*********************线索化结束*********************************/

}
//测试二叉树的遍历

/**
 * 〈线索化二叉树测试〉
 *
 * @author nick
 * @create 2019/9/17
 * @since 1.0.0
 */
public class ThreadedBinaryTreeTest extends Tester {
    @Test
    public void testPolandNotation() throws Exception {

        //测试一把中序线索二叉树的功能 以数组{8, 3, 10, 1, 14, 6}为例

        /**
         *          1
         *        /            *       3     6
         *      / \   /
         *     8  10 14
         */

        HeroNode root = new HeroNode(1, "java");
        HeroNode node2 = new HeroNode(3, "C#");
        HeroNode node3 = new HeroNode(6, "Python");
        HeroNode node4 = new HeroNode(8, "C++");
        HeroNode node5 = new HeroNode(10, "GO");
        HeroNode node6 = new HeroNode(14, "Dephi");

        //二叉树,后面我们要递归创建, 现在简单处理使用手动创建
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        //*************测试中序线索化***************//

        System.out.println("==========中序线索化开始=============");
        System.out.println("{8, 3, 10, 1, 14, 6}");
        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRoot(root);
        threadedBinaryTree.threadedNodes();

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

        //当线索化二叉树后,能在使用原来的遍历方法
        //threadedBinaryTree.infixOrder();
        System.out.println("中序使用线索化的方式遍历 线索化二叉树");
        threadedBinaryTree.threadedList(); // 8, 3, 10, 1, 14, 6
        //********************中序结束******************//

        //******************前序*****************//
        System.out.println("==========前序线索化开始=============");
        System.out.println("{1,3,8,10,6,14}");

        //前序:{1,3,8,10,6,14}
        ThreadedBinaryTree threadedBinaryTreePre = new ThreadedBinaryTree();
        threadedBinaryTreePre.setRoot(root);
        threadedBinaryTreePre.threadedNodesPre();

        //测试: 以10号节点测试
        HeroNode leftNodePre = node4.getLeft();
        HeroNode rightNodePre = node4.getRight();
        System.out.println("8号结点的前驱结点是 =" + leftNodePre); //3
        System.out.println("8号结点的后继结点是=" + rightNodePre); //10

        HeroNode leftNodetenPre = node5.getLeft();
        HeroNode rightNodetenPre = node5.getRight();
        System.out.println("10号结点的前驱结点是 =" + leftNodetenPre); //8
        System.out.println("10号结点的后继结点是=" + rightNodetenPre); //6

        System.out.println("前序使用线索化的方式遍历 线索化二叉树");
        threadedBinaryTreePre.threadedListPre();//{1,3,8,10,6,14}

        //******************前序结束*****************//

        //******************后序*****************//

        //如果是后序,需要创建二叉树的时候,将parent进行保存。这个是用于后续二叉树的遍历的

        node2.setParent(root);
        node3.setParent(root);
        node4.setParent(node2);
        node5.setParent(node2);
        node6.setParent(node3);

        System.out.println("==========后序线索化开始=============");
        System.out.println("{8,10,3,14,6,1}");
        //后序:{8,10,3,14,6,1}
        ThreadedBinaryTree threadedBinaryTreeAfter = new ThreadedBinaryTree();
        threadedBinaryTreeAfter.setRoot(root);
        threadedBinaryTreeAfter.threadedNodesAfter();

        HeroNode leftNodeAfter = node4.getLeft();
        HeroNode rightNodeAfter = node4.getRight();
        System.out.println("8号结点的前驱结点是 =" + leftNodeAfter); //null
        System.out.println("8号结点的后继结点是=" + rightNodeAfter); //10

        HeroNode leftNodetenAfter = node5.getLeft();
        HeroNode rightNodetenAfter = node5.getRight();
        System.out.println("10号结点的前驱结点是 =" + leftNodetenAfter); //8
        System.out.println("10号结点的后继结点是=" + rightNodetenAfter); //3

        System.out.println("后序使用线索化的方式遍历 线索化二叉树");
        threadedBinaryTreeAfter.threadedListAfter();//{8,10,3,14,6,1}

    }
}
  • 前序和中序差不多,比较好理解。后序比较难以理解。不过结合代码后,断点执行,看一下过程,对理解有好处。特别注意是后序遍历要在二叉树创建的时候,将parent进行保存设置。

原文地址:https://blog.51cto.com/yushiwh/2439518

时间: 2024-10-26 01:14:12

java实现线索化二叉树的前序、中序、后续的遍历(完整代码)的相关文章

二叉树遍历,深度有限遍历,广度优先遍历,前序中序后续优先遍历,层次遍历

首先明白两个概念: 1. 深度遍历包括前中后序遍历三种: 2. 广度优先遍历就是层次遍历. PS: 前中后序遍历,如果使用递归遍历,都很简单易理解: 如果使用非递归方式,首先想到的就应该是使用栈结构来控制整个过程,因为递归也是利用栈来实现的: 前中后序遍历的非递归方式中,后序遍历的非递归方式相比较而言,略复杂. 直接上代码: #include "stdlib.h" #include <iostream> #include <stack> #include <

数据结构之二叉树(前序 中序 后续线索话非递归方式)

节点: enum LinkType {                  THREAD,                  LINK }; template<class T> struct ThredBinaryNode {                  ThredBinaryNode *_left;                  ThredBinaryNode *_right;                  LinkType _left_tag;                 

二叉树的前序/中序/后续遍历(递归+非递归)

这几日又把二叉树的递归写了一遍,原来是用C写的,自己写了一个栈,这一次直接用的C++,使用了自带的栈结构.代码如下: 1 /************************************************************************* 2 > Author: Yves 3 > E-mail: [email protected] 4 > File Name: BiTreeNew.cpp 5 > Description: ... 6 > Cre

面试之路(11)-java递归和非递归二叉树前序中序后序遍历

二叉树的遍历 对于二叉树来讲最主要.最基本的运算是遍历. 遍历二叉树 是指以一定的次序访问二叉树中的每个结点.所谓 访问结点 是指对结点进行各种操作的简称.例如,查询结点数据域的内容,或输出它的值,或找出结点位置,或是执行对结点的其他操作.遍历二叉树的过程实质是把二叉树的结点进行线性排列的过程.假设遍历二叉树时访问结点的操作就是输出结点数据域的值,那么遍历的结果得到一个线性序列. 从二叉树的递归定义可知,一棵非空的二叉树由根结点及左.右子树这三个基本部分组成.因此,在任一给定结点上,可以按某种次

数据结构之二叉树 (构造 拷贝构造 以及前序中序后续三种遍历方法)

首先二叉树的节点定义如下: struct BinaryNode {                  BinaryNode *_left;                  BinaryNode *_right;                  T _data;                 BinaryNode( T data ) :_data(data), _left( NULL), _right(NULL )                 {}; }; 二叉树的结构以及接口如下 te

JAVA递归实现线索化二叉树

JAVA递归实现线索化二叉树 基础理论 首先,二叉树递归遍历分为先序遍历.中序遍历和后序遍历. 先序遍历为:根节点+左子树+右子树 中序遍历为:左子树+根节点+右子树 后序遍历为:左子树+右子树+根节点 (只要记住根节点在哪里就是什么遍历,且都是先左再右) 线索化 现在有这么一棵二叉树,它的数据结构由左节点+权+右节点构成. 可以看到4,5,6,7这几个节点的左右节点空间被浪费了.因此,线索化是指有效利用这些空间. 中序遍历的顺序为:4 2 5 1 6 3 7 现在引入前驱节点以及后继节点. 前

中序线索化二叉树

中序线索化二叉树 1 void Tree::_inTree(Node * root, Node * &pre) { 2 if (root == NULL) { // 结点为空, 1:二叉树为空 2:已到达右子树的最后一个右结点的 rchild 3 return; 4 } 5 _inTree(root->lchild, pre); // 到达当前结点的左子树的底部左结点 6 if (root->lchild == NULL) { 7 root->ltag = nChild; //

数据结构例程——线索化二叉树(中序)

本文是数据结构基础系列(6):树和二叉树中第14课时线索二叉树的例程. #include <stdio.h> #include <malloc.h> #define MaxSize 100 typedef char ElemType; typedef struct node { ElemType data; int ltag,rtag; //增加的线索标记 struct node *lchild; struct node *rchild; } TBTNode; void Creat

线索化二叉树的构建与先序,中序遍历(C++版)

贴出学习C++数据结构线索化二叉树的过程, 方便和我一样的新手进行测试和学习 同时欢迎各位大神纠正. 不同与普通二叉树的地方会用背景色填充 //BinTreeNode_Thr.h 1 enum PointTag 2 {Link,Thread}; 3 4 template<typename ElemType> 5 struct BinTreeNode 6 { 7 ElemType data; //数据元素 8 PointTag LTag,RTag; //左标志,右标志 9 BinTreeNode