通過前、中、後序遍歷序列中的任意兩個構造二叉樹【Java實現】

二叉樹的前序、中序和後序遍歷是二叉樹學習中的一個重點,也是二叉樹中很基礎的一部分,需要我們熟練的運用;通常已知二叉樹,很多同學都能很輕鬆的得到其前序、中序和後序遍歷的隊列,甚至層序遍歷也不在話下,如果是反過來呢?如果是理解不夠的同學就會覺得頭疼,有點不知道怎麼下手,這裏我將分享一下我的彙總,可能方法不是最優的,但是一定能幫助理解。

在此之前呢,我們需要再回顧一下二叉樹的前序、中序和後序遍歷的特點;

前序遍歷:遍歷後的序列中節點按照[ 根節點 | 左子樹 | 右子樹 ] 排序,以上圖所示爲例[ 1 | 2  4  5 | 3  6  7 ]

中序遍歷:遍歷後的序列中節點按照[ 左子樹 | 根節點 | 右子樹 ] 排序,以上圖所示爲例[ 4  2  5 | 1 | 6  3  7 ]

後序遍歷:遍歷後的序列中節點按照[ 左子樹 | 右子樹 | 根節點 ] 排序,以上圖所示爲例[ 4  5  2 | 6  7  3 | 1 ]

前序和中序

解題思路

結合前序遍歷和中序遍歷的特點,可以順序完成下列工作:

  1. 前序遍歷的首個元素就是根節點 root 的值;
  2. 在中序遍歷中找到根節點 root 的索引,可將中序隊列中左子樹和右子樹劃分出來,即[ 左子樹 | 根節點 | 右子樹 ]
  3. 根據中序隊列中左(右)子樹節點的數量,可將前序隊列中左子樹和右子樹劃分出來,即[ 根節點 | 左子樹 | 右子樹 ];

根據子樹特點,我們可以通過同樣的方法對左(右)子樹進行劃分,每輪可確認三個節點的關係 。此遞推性質讓我們聯想到用 遞歸方法 處理;但是在開始之前還有一個很重要的事情,就是先對遞歸進行解析;

  • 遞推參數: 前序遍歷中根節點的索引 pre_root、中序遍歷左邊界 in_left、中序遍歷右邊界 in_right。
  • 終止條件: 當 in_left > in_right ,子樹中序遍歷爲空,說明已經越過葉子節點,此時返回 nullnull 。
  • 遞推工作:
  1. 建立根節點 root: 值爲前序遍歷中索引爲 pre_root 的節點值。
  2. 搜索根節點root 在中序遍歷的索引 i: 爲了提升搜索效率,本題解使用哈希表 dic 預存儲中序遍歷的值與索引的映射關係,每次搜索的時間複雜度爲 O(1)。
  3. 構建根節點root的左子樹和右子樹: 通過調用 recur() 方法開啓下一層遞歸。
    1. 左子樹: 根節點索引爲 pre_root + 1 ,中序遍歷的左右邊界分別爲 in_left 和 i - 1。
    2. 右子樹: 根節點索引爲 i - in_left + pre_root + 1(即:根節點索引 + 左子樹長度 + 1),中序遍歷的左右邊界分別爲 i + 1 和 in_right。
  • 返回值: 返回 root,含義是當前遞歸層級建立的根節點 root 爲上一遞歸層級的根節點的左或右子節點
class Solution {
    //定義一個map用來存儲中序序列,方便直接利用根節點的值找其在中序序列中的索引
    HashMap<Integer, Integer> in_map = new HashMap<>();
    //定義一個全局變量preArr,用於指向前序序列,方便在recur方法中使用
    int[] preArr;

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        preArr = preorder;
        for(int i = 0; i < inorder.length; i++)
            in_map.put(inorder[i], i);
        return recur(0, 0, inorder.length-1);
    }

    /**
    * recur方法開啓下一層遞歸
    * pre_root 前序遍歷中根節點的索引
    * in_left 中序遍歷左邊界
    * in_right 中序遍歷右邊界
    */
    public TreeNode recur(int pre_root, int in_left, int in_right){
        //遞歸先判斷結束條件;當 in_left > in_right ,子樹中序遍歷爲空,說明已經越過葉子節點,此時返回 null 。
        if(in_left > in_right)
            return null;
        //建立當前子樹的根節點
        TreeNode root = new TreeNode(preArr[pre_root]);
        //搜索根節點在中序遍歷中的索引,從而可對根節點、左子樹、右子樹完成劃分
        int i = in_map.get(preArr[pre_root]);
        //開啓左子樹的下層遞歸
        root.left = recur(pre_root+1, in_left, i-1);
        //開啓右子樹的下層遞歸
        root.right = recur(pre_root+i-in_left+1, i+1, in_right);
        return root;
    }
}

中序和後序

後序和前序是比較類似的序列,前序序列的第一個元素爲樹的根節點,而後序序列的根節點則是在最後一個元素;因此,如果掌握了前序和中序,那麼中序和後序的原理也基本相同,唯一的區別就是位置關係的計算,結合下面這張圖去看代碼可以幫助大家去理解;

class Solution {
    HashMap<Integer, Integer> in_map = new HashMap<>();
    int[] posArr;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        posArr = postorder;
        for(int i = 0; i < inorder.length; i++)
            in_map.put(inorder[i], i);
        return recur(postorder.length-1, 0, 0, inorder.length-1);
    }

    public TreeNode recur(int pre_root, int pre_left, int in_left, int in_right){
        if(in_left > in_right || pre_root < pre_left)
            return null;
        TreeNode root = new TreeNode(posArr[pre_root]);
        int i = in_map.get(posArr[pre_root]);
        root.left = recur(pre_left+i-in_left-1, pre_left, in_left, i-1);
        root.right = recur(pre_root-1, pre_left+i-in_left, i+1, in_right);
        return root;
    }
}

前序和後序

前序和後序這兩個序列比較相似,都能直接找到根節點,而沒有任何一個序列能夠將左右子樹進行分離,所以實現起來更爲麻煩;具體思路爲,我們在前序序列中可以找到根節點和左子樹的根節點(根節點後面的節點),我們可以在後序序列中找到這個左子樹根節點的索引,從而得到左子樹的長度,這樣就可以分別在前序和後序序列中將根節點、左子樹和右子樹劃分出來,我們還結合圖形進行理解;

class Solution {
    HashMap<Integer, Integer> preMap = new HashMap<>();
    HashMap<Integer, Integer> postMap = new HashMap<>();
    int[] preArr;
    public TreeNode constructFromPrePost(int[] pre, int[] post) {
        preArr = pre;
        for(int i = 0; i < pre.length; i++)
            preMap.put(pre[i], i);
        for(int i = 0; i < post.length; i++)
            postMap.put(post[i], i);
        return recur(0, pre.length-1, 0, post.length-1);
    }

    public TreeNode recur(int pre_left, int pre_right, int post_left, int post_right){
        if(pre_left > pre_right || post_left > post_right)
            return null;
        //這裏需要注意,新增加了兩種判斷情況,因爲如果相等的時候證明這已經是葉子節點無需再向下遞歸
        //因爲left和right都是計算出來的,所以再遞歸也不會滿足left>right,而會出現數組下標越界的情況
        if(pre_left == pre_right)
        	return new TreeNode(preArr[pre_left]);
        if(post_left == post_right)
        	return new TreeNode(preArr[post_left]);
        
        TreeNode root = new TreeNode(preArr[pre_left]);
        int left_len = postMap.get(preArr[pre_left+1]) - post_left;
        root.left = recur(pre_left+1, pre_left+1+left_len, post_left, postMap.get(preArr[pre_left+1]));
        root.right = recur(pre_left+left_len+2, pre_right, postMap.get(preArr[pre_left+1])+1, post_right-1);
        return root;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章