9-3线索化二叉树

线索化

引出

一个问题:

1621593085518

1621593183644

基本介绍

1621597431786

图解

1621598764919

应用

1621597780006

代码实现

package com.company.tree;

/**
 * @Title: 二叉树相关操作
 * @Description:
 * @Author: Uncle Ming
 * @Date:2017年1月6日 下午2:49:14
 * @Version V1.0
 */
public class ThreadedBinaryTree {

    private Node preNode;   //线索化时记录前一个节点

    //节点存储结构
    static class Node {
        String data;        //数据域
        Node left;          //左指针域
        Node right;         //右指针域
        boolean isLeftThread = false;   //左指针域类型  false:指向子节点、true:前驱或后继线索
        boolean isRightThread = false;  //右指针域类型  false:指向子节点、true:前驱或后继线索

        Node(String data) {
            this.data = data;
        }
    }

    /**
     * 通过数组构造一个二叉树(完全二叉树)
     * @param array
     * @param index
     * @return
     */
    static Node createBinaryTree(String[] array, int index) {
        Node node = null;

        if(index < array.length) {
            node = new Node(array[index]);
            node.left = createBinaryTree(array, index * 2 + 1);
            node.right = createBinaryTree(array, index * 2 + 2);
        }

        return node;
    }

    /**
     * 中序线索化二叉树
     * @param node  节点
     */
    void inThreadOrder(Node node) {
        if(node == null) {
            return;
        }

        //处理左子树
        inThreadOrder(node.left);

        //左指针为空,将左指针指向前驱节点
        if(node.left == null) {
            node.left = preNode;
            node.isLeftThread = true;
        }

        //前一个节点的后继节点指向当前节点
        if(preNode != null && preNode.right == null) {
            preNode.right = node;
            preNode.isRightThread = true;
        }
        preNode = node;

        //处理右子树
        inThreadOrder(node.right);
    }

    /**
     * 中序遍历线索二叉树,按照后继方式遍历(思路:找到最左子节点开始)
     * @param node
     */
    void inThreadList(Node node) {
        //1、找中序遍历方式开始的节点
        while(node != null && !node.isLeftThread) {
            node = node.left;
        }

        while(node != null) {
            System.out.print(node.data + ", ");

            //如果右指针是线索
            if(node.isRightThread) {
                node = node.right;

            } else {    //如果右指针不是线索,找到右子树开始的节点
                node = node.right;
                while(node != null && !node.isLeftThread) {
                    node = node.left;
                }
            }
        }
    }

    /**
     * 中序遍历线索二叉树,按照前驱方式遍历(思路:找到最右子节点开始倒序遍历)
     * @param node
     */
    void inPreThreadList(Node node) {
        //1、找最后一个节点
        while(node.right != null && !node.isRightThread) {
            node = node.right;
        }

        while(node != null) {
            System.out.print(node.data + ", ");

            //如果左指针是线索
            if(node.isLeftThread) {
                node = node.left;

            } else {    //如果左指针不是线索,找到左子树开始的节点
                node = node.left;
                while(node.right != null && !node.isRightThread) {
                    node = node.right;
                }
            }
        }
    }

    /**
     * 前序线索化二叉树
     * @param node
     */
    void preThreadOrder(Node node) {
        if(node == null) {
            return;
        }

        //左指针为空,将左指针指向前驱节点
        if(node.left == null) {
            node.left = preNode;
            node.isLeftThread = true;
        }

        //前一个节点的后继节点指向当前节点
        if(preNode != null && preNode.right == null) {
            preNode.right = node;
            preNode.isRightThread = true;
        }

        preNode = node;

        //处理左子树
        if(!node.isLeftThread) {
            preThreadOrder(node.left);
        }

        //处理右子树
        if(!node.isRightThread) {
            preThreadOrder(node.right);
        }
    }

    /**
     * 前序遍历线索二叉树(按照后继线索遍历)
     * @param node
     */
    void preThreadList(Node node) {
        while(node != null) {

            while(!node.isLeftThread) {
                System.out.print(node.data + ", ");
                node = node.left;
            }

            System.out.print(node.data + ", ");
            node = node.right;
        }
    }

    public static void main(String[] args) {
        String[] array = {"A", "B", "C", "D", "E", "F", "G", "H"};
        Node root = createBinaryTree(array, 0);

        ThreadedBinaryTree tree = new ThreadedBinaryTree();
        tree.inThreadOrder(root);
        System.out.println("中序按后继节点遍历线索二叉树结果:");
        tree.inThreadList(root);
        System.out.println("\n中序按后继节点遍历线索二叉树结果:");
        tree.inPreThreadList(root);

        Node root2 = createBinaryTree(array, 0);
        ThreadedBinaryTree tree2 = new ThreadedBinaryTree();
        tree2.preThreadOrder(root2);
        tree2.preNode = null;
        System.out.println("\n前序按后继节点遍历线索二叉树结果:");
        tree.preThreadList(root2);
    }
}

后续线索二叉树

遍历后序线索化二叉树的思路:

由于是后序线索化,那么后序遍历的开始节点一定是最左子节点,从根节点出发找到最左子节点,如何判断是否是最左子节点呢?如果是最左子节点,则其left指针一定的线索,如上图我们找到最左子节点H,H的right指针是后继线索,找到节点I,节点I的right指针是后继线索,找到节点D,节点D的right指针是子节点I,并不是后继线索指针,那么问题来了?此时我们该如何处理呢?

通过观察D的后继节点E,但是D与E没有直接线索,不过D的父节点是B,B的右字节是E,存在这样一个间接的关系,我们是否可以利用这个间接的关系呢?答案是肯定的,但是按照我们上文介绍的节点数据结构,并不存在指向父节点的指针,因此我们要对节点数据结构进行修改,修改如下:

//节点存储结构
static class Node {
    String data;        //数据域
    Node left;          //左指针域
    Node right;         //右指针域
    Node parent;        //父节点的指针(为了后序线索化使用)
    boolean isLeftThread = false;   //左指针域类型  false:指向子节点、true:前驱或后继线索
    boolean isRightThread = false;  //右指针域类型  false:指向子节点、true:前驱或后继线索

    Node(String data) {
        this.data = data;
    }
}
----------------------------------------------------------------------------------------------------
package com.bj58.demo.struct;

/**
 * @Title: 后序线索化二叉树相关操作
 * @Description:
 * @Author: Uncle Ming
 * @Date:2017年1月8日 下午3:42:14
 * @Version V1.0
 */
public class PostThreadBinaryTree {

    private Node preNode;   //线索化时记录前一个节点

    //节点存储结构
    static class Node {
        String data;        //数据域
        Node left;          //左指针域
        Node right;         //右指针域
        Node parent;        //父节点的指针(为了后序线索化使用)
        boolean isLeftThread = false;   //左指针域类型  false:指向子节点、true:前驱或后继线索
        boolean isRightThread = false;  //右指针域类型  false:指向子节点、true:前驱或后继线索

        Node(String data) {
            this.data = data;
        }
    }

    /**
     * 通过数组构造一个二叉树(完全二叉树)
     * @param array
     * @param index
     * @return
     */
    static Node createBinaryTree(String[] array, int index) {
        Node node = null;

        if(index < array.length) {
            node = new Node(array[index]);
            node.left = createBinaryTree(array, index * 2 + 1);
            node.right = createBinaryTree(array, index * 2 + 2);

            //记录节点的父节点(后序线索化遍历时使用)
            if(node.left != null) {
                node.left.parent = node;
            }

            if(node.right != null) {
                node.right.parent = node;
            }
        }

        return node;
    }

    /**
     * 后序线索化二叉树
     * @param node  节点
     */
    void postThreadOrder(Node node) {
        if(node == null) {
            return;
        }

        //处理左子树
        postThreadOrder(node.left);
        //处理右子树
        postThreadOrder(node.right);

        //左指针为空,将左指针指向前驱节点
        if(node.left == null) {
            node.left = preNode;
            node.isLeftThread = true;
        }

        //前一个节点的后继节点指向当前节点
        if(preNode != null && preNode.right == null) {
            preNode.right = node;
            preNode.isRightThread = true;
        }
        preNode = node;
    }

    /**
     * 后续遍历线索二叉树,按照后继方式遍历(思路:后序遍历开始节点是最左节点)
     * @param node
     */
    void postThreadList(Node root) {
        //1、找后序遍历方式开始的节点
        Node node = root;
        while(node != null && !node.isLeftThread) {
            node = node.left;
        }

        Node preNode = null;
        while(node != null) {
            //右节点是线索
            if(node.isRightThread) {
                System.out.print(node.data + ", ");
                preNode = node;
                node = node.right;

            } else {
                //如果上个处理的节点是当前节点的右节点
                if(node.right == preNode) {
                    System.out.print(node.data + ", ");
                    if(node == root) {
                        return;
                    }

                    preNode = node;
                    node = node.parent;

                } else {    //如果从左节点的进入则找到有子树的最左节点
                    node = node.right;
                    while(node != null && !node.isLeftThread) {
                        node = node.left;
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        String[] array = {"A", "B", "C", "D", "E", "F", "G", "H", "I"};
        Node root = createBinaryTree(array, 0);

        PostThreadBinaryTree tree = new PostThreadBinaryTree();
        tree.postThreadOrder(root);
        System.out.println("后序按后继节点遍历线索二叉树结果:");
        tree.postThreadList(root);
    }
}

前序、中序、后序线索化比较

  1. 前序线索化二叉树遍历相对最容易理解,实现起来也比较简单。由于前序遍历的顺序是:根左右,所以从根节点开始,沿着左子树进行处理,当子节点的left指针类型是线索时,说明到了最左子节点,然后处理子节点的right指针指向的节点,可能是右子树,也可能是后继节点,无论是哪种类型继续按照上面的方式(先沿着左子树处理,找到子树的最左子节点,然后处理right指针指向),以此类推,直到节点的right指针为空,说明是最后一个,遍历完成。
  2. 中序线索化二叉树的网上相关介绍最多。中序遍历的顺序是:左根右,因此第一个节点一定是最左子节点,先找到最左子节点,依次沿着right指针指向进行处理(无论是指向子节点还是指向后继节点),直到节点的right指针为空,说明是最后一个,遍历完成。
  3. 后序遍历线索化二叉树最为复杂,通用的二叉树数节点存储结构不能够满足后序线索化,因此我们扩展了节点的数据结构,增加了父节点的指针。后序的遍历顺序是:左右根,先找到最左子节点,沿着right后继指针处理,当right不是后继指针时,并且上一个处理节点是当前节点的右节点,则处理当前节点的右子树,遍历终止条件是:当前节点是root节点,并且上一个处理的节点是root的right节点。
posted @ 2021-11-23 21:20  剪水行舟  阅读(62)  评论(0)    收藏  举报