Java实现前中后序线索化二叉树以及遍历

x33g5p2x  于2021-11-22 转载在 Java  
字(5.1k)|赞(0)|评价(0)|浏览(430)

一、线索化二叉树的原理

在前面介绍二叉树的文章中提到,二叉树可以使用两种存储结构:顺序存储和链式存储,在使用链式存储时,会存在大量的空指针域,n个节点的二叉链表中含有n+1[ 2n-(n-1)=n+1 ]个空指针域,为了可以充分利用这些空指针域,引申出了线索化二叉树,将这些空指针域利用起来,提高检索效率。

上图中,紫色区域就是没有指向的空指针域,从存储空间的角度来看,这些空指针域浪费了内存资源。

从另外的角度思考,如果需要知道C节点的前驱节点和后继节点,每次都都需要进行一次遍历,是否可以提前存储C节点的前驱节点和后继节点从而省去遍历的操作从而提高效率呢?

综合以上分析,可以通过充分利用二叉链表中的空指针域,存入节点在某种遍历方式下的前驱和后继节点的指针。

这种指向前驱和后继的指针成为线索,加上线索的二叉链成为线索链表,而对应的二叉树就称为线索化二叉树(Threaded Binary Tree)

二、构建线索化二叉树

  • 先对二叉树进行中序遍历(不了解二叉树遍历的可以参考数据结构:二叉树),将所有节点右指针为空的指针域指针它的后继节点,如下图:

通过中序遍历到D节点,得到了D节点right指针为空,并且D节点后继节点为B,于是将D的right节点指向B节点(如上图中的①操作),而B节点right指针也为空,于是将right指针指向A节点(如上图中的②操作),以此类推,到F节点的后继节点为Null,则F的right指针指向Null。

  • 接下来将这颗二叉树所有左指针为空的指针指向它的前驱节点,如下图:

如上图,D节点的left指针域指向Null(第⑤部操作),E节点的前驱节点是A,将E的left指针指向A节点,以此类推,F节点的left节点指向C节点。

  • 通过上述两步操作完成了整个二叉树的线索化。

通过线索化后,可以看出(蓝色箭头表示前驱,粉色箭头表示后继)线索化二叉树相当于是把二叉树转换成一个特殊的双向链表,这样对新增、删除以及查找节点都带来了方便,提高了效率,转换为链表后的图示如下:

线索化需要对节点的结构进行修改,修改之后的数据结构如下:

class Node
{
    private int num;
    private Object data;	//数据域
    private Node left;		//左指针域
    private Node right;		//右指针域

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

三、代码实现线索二叉树

  • 节点结构
class Node
{
    private int num;
    private Object data;
    private Node left;
    private Node right;

    /** * leftType == 0表示指向的是左子树,如果是1表示指向前驱节点 */
    private int leftType;

    /** * rightType == 0表示指向的是右子树,如果是1表示指向后继节点 */
    private int rightType;

    public Node(int num, Object data)
    {
        this.num = num;
        this.data = data;
    }

    /** * 前序遍历 */
    public void preOrder()
    {
        //先输出父节点信息
        System.out.println(this);
        //判断此节点的左节点是否为空
        //如果左节点不为空并且指针类型不是前驱类型
        if (this.left != null && this.leftType != 1)
        {
            //向左前序遍历
            this.left.preOrder();
        }
        //判断此节点的右节点是否为空
        //如果右节点不为空并且指针类型不是后继类型
        if (this.right != null && this.rightType != 1)
        {
            //向右前序遍历
            this.right.preOrder();
        }
    }

    /** * 中序遍历线索化二叉树 */
    public void infixOrder()
    {
        //判断此节点的左节点是否为空
        if (this.left != null && this.leftType != 1)
        {
            //向左中序遍历
            this.left.infixOrder();
        }
        //输出父节点信息
        System.out.println(this);
        //判断此节点的右节点是否为空
        if (this.right != null && this.rightType != 1)
        {
            //向右中序遍历
            this.right.infixOrder();
        }
    }

    /** * 后序遍历 */
    public void postOrder()
    {
        //判断此节点的左节点是否为空
        if (this.left != null && this.leftType != 1)
        {
            //向左后序遍历
            this.left.postOrder();
        }
        //判断此节点的右节点是否为空
        if (this.right != null && this.rightType != 1)
        {
            //向右后序遍历
            this.right.postOrder();
        }
        //输出父节点信息
        System.out.println(this);
    }
	
    //get set方法省略
    
    @Override
    public String toString()
    {
        return "Node{" +
                "num=" + num +
                ", data=" + data +
                '}';
    }
}
  • 线索化二叉树结构
/** * 定义ThreadedBinaryTree 实现了线索化功能的二叉树 */
class ThreadedBinaryTree
{
    /** * 根节点 */
    private Node root;

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

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

    public void createBinaryTree(ArrayList<Node> list)
    {
        this.createBinaryTree(list,0);
    }

    /** * 创建二叉树 * @param list 节点列表 * @param index 用于创建的索引 * @return */
    private Node createBinaryTree(ArrayList<Node> list, int index)
    {
        Node node = null;

        if (isEmpty())
        {
            setRoot(list.get(0));
        }

        if (index < list.size())
        {
            node = list.get(index);
            node.setLeft(createBinaryTree(list, (2*index+1)));
            node.setRight(createBinaryTree(list, (2*index+2)));
        }
        return node;
    }

    public boolean isEmpty()
    {
        return root == null;
    }

    public void preThreadNodes()
    {
        this.preThreadNodes(root);
    }

    public void infixThreadNodes()
    {
        this.infixThreadNodes(root);
    }

    public void postThreadNodes()
    {
        this.postThreadNodes(root);
    }

    /** * 前序线索化二叉树 * @param node 当前需要线索化的节点 */
    private void preThreadNodes(Node node)
    {
        //递归回溯条件
        if (node == null)
        {
            return;
        }

        //遍历到叶子节点(前驱指针未利用)
        if (node.getLeft() == null)
        {
            node.setLeft(pre);
            node.setLeftType(1);
        }

        //pre.getLeft() != node 一定不可少
        //否则在第一次回溯后到父节点后,如果不加上此判断会让pre节点后驱指针指向该父节点
        //此时pre的前驱后继节点均指向该父节点,必然会发生死循环无法结束递归
        if (pre != null && pre.getRight() == null && pre.getLeft() != node)
        {
            pre.setRight(node);
            pre.setRightType(1);
        }

        pre = node;

        if (node.getLeftType() == 0)
        {
            preThreadNodes(node.getLeft());
        }

        preThreadNodes(node.getRight());
    }

    /** * 中序线索化二叉树 * @param node 当前需要线索化的节点 */
    private void infixThreadNodes(Node node)
    {
        if (node == null)
        {
            return;
        }
        //1.递归线索化左子树
        infixThreadNodes(node.getLeft());

        //2.线索化当前节点
        //处理当前节点的前驱节点
        if (node.getLeft() == null)
        {
            //让当前节点的左指针指向前驱节点
            node.setLeft(pre);
            //修改当前节点的左指针的类型
            node.setLeftType(1);
        }
        //处理后继节点
        if (pre != null && pre.getRight() == null)
        {
            //让前驱节点的右指针指向当前节点
            pre.setRight(node);
            //修改前驱节点的右指针类型
            pre.setRightType(1);
        }

        //每处理一个节点之后让当前节点是下一个节点的前驱节点
        pre = node;

        //3.递归线索化右子树
        infixThreadNodes(node.getRight());
    }

    /** * 后序线索化二叉树 * @param node 当前需要线索化的节点 */
    private void postThreadNodes(Node node)
    {
        if (node == null)
        {
            return;
        }

        postThreadNodes(node.getLeft());
        postThreadNodes(node.getRight());

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

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


    /** * 非递归方式中序遍历线索化二叉树的方法 */
    public void threadedInfixList()
    {
        Node node = root;
        while (node != null)
        {
            //循环地找到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();
        }
    }

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

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

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

以上。

如有不足或者错误欢迎评论指正。

整理不易,留个三连再走吧!

相关文章