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進行保存設置。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章