后序線索化二叉樹(Java版)


      前面介紹了前序線索化二叉樹、中序線索化二叉樹,本文將介紹后序線索化二叉樹。之所以用單獨的一篇文章來分析后序線索化二叉樹,是因為后序線索化二叉樹比前序、中序要復雜一些;另外在復習線索化二叉樹的過程中,大部分講解數據結構的書籍中都是以中序線索化為例,在網上搜索也很少有詳細講解前序、后序線索化的文章,對於使用Java語言編寫的代碼更是鳳毛麟角,因此決定把個人的理解過程記錄下,並分享給有需要的同學參考。

一、圖解后序線索化

      如果你很清楚的理解了前序、中序線索化二叉樹,那么下面的圖解不難理解;如果你還未掌握前序、中序線索化二叉樹,請先詳細了解線索二叉樹之前序、中序線索化,然后再回來閱讀本文更便於理解。 
      下圖是一棵后序線索化的二叉樹,如下圖: 

è¿éåå¾çæè¿°

      為了更清晰、直觀的表示出后繼線索,在上圖中忽略了前驅線索,請自行腦補。通過觀察上圖,節點H的后繼節點是I,因此節點H的right指針指向I;節點I的后繼節點是D,因此節點D的right指針指向D;節點D的后繼節點是E,但是節點D的right指針指向了子節點B,因此D的right指針也就不能指向后繼節點;同理節點B也沒辦法指向后繼節點F。 
      對這棵二叉樹完成后序線索化之后,我們在對其進行遍歷時,我們知道后序遍歷的順序是:左右根,那對於上圖的后序遍歷結果是:HIDEBFGCA
      遍歷后序線索化二叉樹的思路:由於是后序線索化,那么后序遍歷的開始節點一定是最左子節點,從根節點出發找到最左子節點,如何判斷是否是最左子節點呢?如果是最左子節點,則其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;
    }
}

      按照如上的存儲結構增加了parent指針之后,D節點存在了指向父節點B的指針。當遍歷到D節點時找到D節點的父節點B,B的right指針指向了子節點E,E的right指針又指向了B,這里又出現了另一個問題,就是進入了兩次B,如果按照前面的方式則進入了一個死循環。以節點B為例,我們什么時候去找B的父節點,什么時候去處理他的右節點呢。我們分析下兩次進入節點B,第一次是通過B的左節點進入,第二次是通過右子節點進入,我們可以記錄上一個處理的節點,如果上一個處理的節點是B的左節點,則接下進入B的右節點,如果上一個處理的節點是B的右節點,則說明B的左右子樹都處理完成,繼續處理B的父節點。

二、Java代碼實現后序線索化

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


運行結果如下:

后序按后繼節點遍歷線索二叉樹結果:
H, I, D, E, B, F, G, C, A, 


三、前序、中序、后序線索化比較

1. 前序線索化二叉樹遍歷相對最容易理解,實現起來也比較簡單。由於前序遍歷的順序是:根左右,所以從根節點開始,沿着左子樹進行處理,當子節點的left指針類型是線索時,說明到了最左子節點,然后處理子節點的right指針指向的節點,可能是右子樹,也可能是后繼節點,無論是哪種類型繼續按照上面的方式(先沿着左子樹處理,找到子樹的最左子節點,然后處理right指針指向),以此類推,直到節點的right指針為空,說明是最后一個,遍歷完成。 
2. 中序線索化二叉樹的網上相關介紹最多。中序遍歷的順序是:左根右,因此第一個節點一定是最左子節點,先找到最左子節點,依次沿着right指針指向進行處理(無論是指向子節點還是指向后繼節點),直到節點的right指針為空,說明是最后一個,遍歷完成。 
3. 后序遍歷線索化二叉樹最為復雜,通用的二叉樹數節點存儲結構不能夠滿足后序線索化,因此我們擴展了節點的數據結構,增加了父節點的指針。后序的遍歷順序是:左右根,先找到最左子節點,沿着right后繼指針處理,當right不是后繼指針時,並且上一個處理節點是當前節點的右節點,則處理當前節點的右子樹,遍歷終止條件是:當前節點是root節點,並且上一個處理的節點是root的right節點。

轉載自:https://blog.csdn.net/UncleMing5371/article/details/54291221
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM