根據前序遍歷序列、中序遍歷序列,重建二叉樹

辣辣寫程式碼_nanaProgrammer發表於2020-10-06

題目

來自劍指Offer的第7題。
在這裡插入圖片描述

知識儲備

要完成這道題,對於二叉樹的前序遍歷和後序遍歷一定要理解到位,下方連結是我之前寫的文章,請在做該題目之前閱讀完以下文章。
二叉樹前序、中序、後序遍歷

總體思路

二叉樹的前序遍歷順序是:根節點、左子樹、右子樹,每個子樹的遍歷順序同樣滿足前序遍歷順序。

二叉樹的中序遍歷順序是:左子樹、根節點、右子樹,每個子樹的遍歷順序同樣滿足中序遍歷順序。

前序遍歷的第一個節點是根節點,只要找到根節點在中序遍歷中的位置,在根節點之前被訪問的節點都位於左子樹,在根節點之後被訪問的節點都位於右子樹,由此可知左子樹和右子樹分別有多少個節點。

由於樹中的節點數量與遍歷方式無關,通過中序遍歷得知左子樹和右子樹的節點數量之後,可以根據節點數量得到前序遍歷中的左子樹和右子樹的分界,因此可以進一步得到左子樹和右子樹各自的前序遍歷和中序遍歷,可以通過遞迴的方式,重建左子樹和右子樹,然後重建整個二叉樹。
根據前序遍歷序列、中序遍歷序列,重建二叉樹

根據前序遍歷序列、中序遍歷序列,重建二叉樹

幾點思考

思考1:
舉個例子:
如果二叉樹只有3個節點,前序:1,2,3,中序:2,1,3
前序第一個數為根節點,故根節點為1,在中序裡找到根節點1,其左為左子樹,右為右子樹
故這棵樹的形狀:根節點1,左子節點2,右子節點3

思考2:
所謂“重建二叉樹”,就是建立出各個節點,並讓父子節點相連。最後返回整棵樹的根節點。

思考3:
序列特點:
前序序列:根節點-左子樹的節點們-右子樹的節點們(子樹的節點們又可以這樣分)
中序序列:左子樹的節點們-根節點-右子樹的節點們(子樹的節點們又可以這樣分)

思考4:
如何從中序序列陣列裡找出根節點的位置?
選擇使用Map來儲存中序序列陣列的index和值,來快速進行陣列裡值的查詢
思考:為什麼不用Arrays類的binarySearch方法進行二分查詢?如果樹太大了,多次進行二分查詢效率低。

思考5:
遞迴時,處理的陣列是不一樣的(樹不一樣),怎麼實現?
一開始處理的陣列是給出的兩個陣列,後來變成陣列的子陣列,子陣列的子陣列,即樹的子樹,子樹的子樹。
用start、end來指明處理的是陣列中的哪一個部分,隨著遞迴而改變。前序序列陣列有start、end,後序序列陣列也有start、end。

思考6:
遞迴函式要做什麼?
1.更新前序和中序序列的start、end
2.根節點連線左子樹,連線右子樹
3.返回根節點

程式碼

先根據2個前序和中序遍歷的陣列,重建了二叉樹。再呼叫前序遍歷方法、中序遍歷方法,來遍歷此二叉樹。觀察遍歷的順序是否和給出的兩個陣列的順序相同。

import java.util.HashMap;
import java.util.Map;

public class JZO_07 {
    /*07. 重建二叉樹*/
    /*輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。
    假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。*/
    public static void main(String[] args) {
        int[] preorder = {3,9,20,15,7};
        int[] inorder = {9,3,15,20,7};

        //構建二叉樹
        TreeNode root = buildTree(preorder,inorder);

        //先序遍歷
        recursionPreorderTraversal(root);
        System.out.println();
        //中序遍歷
        recursionMiddleorderTraversal(root);
    }

    public static TreeNode buildTree(int[] preorder, int[] inorder) {
        //判斷2個序列是否合法
        if(preorder == null || preorder.length == 0 || inorder == null || inorder.length == 0){
            return null;
        }

        //建立Map放中序序列陣列,便於從中序序列陣列裡找出根節點的位置
        Map<Integer,Integer> inorderIndexMap = new HashMap<Integer, Integer>();
        for(int i=0;i<inorder.length;i++){
            //用陣列的值來找陣列的索引,所以應該將陣列的值當做Map的鍵
            //題目說了節點值不重複,所以作為鍵不會重複
            inorderIndexMap.put(inorder[i],i);
        }

        //遞迴構造樹
        TreeNode root = connectRootAndSubtree(preorder,0,preorder.length-1,
                inorder,0,inorder.length-1,inorderIndexMap);
        return root;
    }

    public static TreeNode connectRootAndSubtree(int[] preorder,int pStart,int pEnd,
                         int[] inorder,int iStart,int iEnd,Map<Integer,Integer> inorderIndexMap){
        //結束遞迴的條件:start、end指標錯位了
        if((pStart > pEnd)||(iStart > iEnd)){
            return null;
        }
        //得到根節點的值
        int rootVal = preorder[pStart];
        //構造根節點
        TreeNode root = new TreeNode(rootVal);
        //得到根節點在中序序列的位置
        int rootIndex = inorderIndexMap.get(rootVal);
        //得到左子樹對應序列的長度(左子樹節點的個數)
        int leftSubtreeLength = rootIndex - iStart;
        //得到右子樹對應序列的長度(右子樹節點的個數)
        int rightSubtreeLength = iEnd - rootIndex;

        //前序序列中,左子樹、右子樹對應的start、end
        //前序序列的左子樹的start
        int pStartLeftSubtree = pStart+1;
        //前序序列的左子樹的end
        int pEndLeftSubtree = pStartLeftSubtree+leftSubtreeLength-1;
        //前序序列的右子樹的start
        int pStartRightSubtree = pEndLeftSubtree+1;
        //前序序列的右子樹的end
        int pEndRightSubtree = pEnd;

        //中序序列的左子樹的start
        int iStartLeftSubtree = iStart;
        //中序序列的左子樹的end
        int iEndLeftSubtree = rootIndex-1;
        //中序序列的右子樹的start
        int iStartRightSubtree = rootIndex+1;
        //中序序列的右子樹的end
        int iEndRightSubtree = iEnd;

        //得到左子樹的根節點
        TreeNode leftSubtreeRoot = connectRootAndSubtree(preorder,pStartLeftSubtree,
                pEndLeftSubtree,inorder,iStartLeftSubtree,iEndLeftSubtree,inorderIndexMap);
        //得到右子樹的根節點
        TreeNode rightSubtreeRoot = connectRootAndSubtree(preorder,pStartRightSubtree,
                pEndRightSubtree,inorder,iStartRightSubtree,iEndRightSubtree,inorderIndexMap);

        //連線根節點與左右子樹
        root.left = leftSubtreeRoot;
        root.right = rightSubtreeRoot;

        return root;
    }

    // 遞迴先序遍歷
    public static void recursionPreorderTraversal(TreeNode root) {
        if (root != null) {
            System.out.print(root.val + " ");
            recursionPreorderTraversal(root.left);
            recursionPreorderTraversal(root.right);
        }
    }

    // 遞迴中序遍歷
    public static void recursionMiddleorderTraversal(TreeNode root) {
        if (root != null) {
            recursionMiddleorderTraversal(root.left);
            System.out.print(root.val + " ");
            recursionMiddleorderTraversal(root.right);
        }
    }
}

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

//執行用時:3 ms, 在所有 Java 提交中擊敗了82.60%的使用者
//記憶體消耗:38.9 MB, 在所有 Java 提交中擊敗了82.98%的使用者

執行結果

在這裡插入圖片描述

畫圖輔助理解:在陣列裡劃分左子樹部分、右子樹部分

在計算前序序列中,左子樹、右子樹對應的start、end;
以及,在計算中序序列中,左子樹、右子樹對應的start、end的時候,
思路容易混亂,我們畫圖來整理一下思路。

根據前序遍歷序列、中序遍歷序列,重建二叉樹

相關文章