【劍指Offer】重建二叉樹

題目描述

輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。

二叉樹的前序中序後序遍歷

做這道題之前我們需要先搞懂二叉樹的前序,中序,後序遍歷是怎樣的。
二叉樹是一顆特殊的樹,它的每個節點都不能有多於兩個的兒子節點。即一顆二叉樹最多有兩個兒子。如下圖所示
在這裏插入圖片描述
而樹有三種遍歷方式,分別是前序,中序,後序遍歷。而前,中,後分別表示的是根節點的遍歷順序。
先遍歷根節點,再左節點,再右節點,叫做前序遍歷
先左節點,再根節點,再右節點,叫做中序遍歷
先左節點,再右節點,再根節點,叫做後序遍歷
以上圖所示的二叉樹爲例,分別描述它的前中後序遍歷

前序遍歷過程

它的前序遍歷爲ABCDEFGH
前序遍歷過程爲,先根節點即A,再左節點即B。目前遍歷順序:AB。此時以B爲根節點的二叉樹,先它的左節點即C,再它的右節點即D。目前遍歷順序:ABCD。此時以D爲根節點的二叉樹,由於它沒有左節點,所以到它的右節點即E。目前遍歷順序:ABCDE。到現在整顆二叉樹的根節點A,左節點B已經遍歷完成,再到它的右節點即F。目前遍歷順序:ABCDF。此時以F爲根節點的二叉樹,先它的左節點即G。目前遍歷順序:ABCDFG。此時以G爲根節點的,到它的右節點即H。最終遍歷順序爲ABCDFGH

中序遍歷過程

中序遍歷爲CBDEAGHF
中序遍歷過程爲,先左節點即B,對於以B爲根節點的二叉樹,又要先它的左節點即C,以C爲根節點的二叉樹沒有兒子節點,所以C是我們遍歷到的第一個節點。目前遍歷順序:C。此時對於以B爲根節點的二叉樹,已經遍歷完它的左節點,再到它的根節點即B。目前遍歷順序:CB。再到它的右節點D,以D爲根節點的二叉樹沒有左節點,所以直接它的根節點D,再到它的右節點即E。目前遍歷順序:CBDE。現在整顆二叉樹的左節點已經遍歷完成,再到它的根節點A。目前遍歷順序:CBDEA。再到它的右節點F,但是以F爲根節點的二叉樹有左節點G,所以先它的左節點G,以G爲根節點的二叉樹沒有左節點,所以先根節點G,再到右節點H。目前遍歷順序:CBDEAGH。此時以F爲根節點的二叉樹,它的左節點已經遍歷完,再到它的根節點F。目前遍歷順序:CBDEAGHF。至此整顆二叉樹遍歷完成

後序遍歷過程

後序遍歷爲CEDBHGFA
後序遍歷過程爲,先左節點即B,對於以B爲根節點的二叉樹,又要先它的左節點即C,以C爲根節點的二叉樹沒有兒子節點,所以C是我們遍歷到的第一個節點。目前遍歷順序:C。此時對於以B爲根節點的二叉樹,已經遍歷完它的左節點,再到它的右節點即D。以D爲根節點的二叉樹沒有左節點,所以到它的右節點即E。目前遍歷順序:CE。此時以D爲根節點的二叉樹右節點已經遍歷完,再到它的根節點即D。目前遍歷順序:CED。此時以B爲根節點的二叉樹左節點右節點均已經遍歷完,再到它的根節點即B。目前遍歷順序:CEDB。此時整顆二叉樹的左節點已經遍歷完成,再到它的右節點即F。此時以F爲根節點的二叉樹又要先它的左節點G,此時以G爲根節點的二叉樹沒有左節點,所以先右節點即H,再遍歷它的根節點即G。目前遍歷順序:CEDBHG。此時以F爲根節點的二叉樹,它的左節點已經遍歷完成,同時沒有右節點,所以到它的根節點即F。目前遍歷順序:CEDBHGF。此時整顆二叉樹,它的左右節點均已遍歷完成,所以到它的根節點A。最終遍歷順序爲:CEDBHGFA

解法1

回到這道題目,已知二叉樹的前序中序遍歷序列,要求重構二叉樹。就以前序遍歷{1,2,4,7,3,5,6,8}和中序遍歷{4,7,2,1,5,3,8,6}構成的二叉樹A爲例來講解解題思路。上面有提到前序遍歷是先根節點再左右節點。所以已知A的前序遍歷{1,2,4,7,3,5,6,8},那麼這顆二叉樹的根節點一定是1。而中序遍歷是先左節點,再根節點,再右節點。已知A中序遍歷爲{4,7,2,1,5,3,8,6},且根節點是1。那麼A的左節點是由1左邊的中序序列{4,7,2}構成的二叉樹A1,A的右節點是由1右邊的中序序列{5,3,8,6}構成的二叉樹A2。
因爲A1的中序序列是{4,7,2},元素個數是3,所以A的前序序列{1,2,4,7,3,5,6,8}中,根節點1後面的三個元素即{2,4,7}就是A1的前序序列。所以A2的前序序列就是{2,4,7}後面的所有元素,即{3,5,6,8}
到現在,我們已經把已知A的前序序列和中序序列求A的問題,轉換爲已知A1的前序中序序列求A1,已知A2的前序中序序列求A2的問題。繼續遞歸下去,直到轉換的子樹前序中序序列只有一個元素,就可以直接表示出這顆子樹了。

實現代碼

public class TreeNode
{
    public int val;
    public TreeNode left;
    public TreeNode right;
    public TreeNode (int x)
    {
        val = x;
    }
}
public TreeNode reConstructBinaryTree(int[] pre, int[] tin)
{
    if (!(pre.Length > 0))
    {
        return null;
    }
    TreeNode root = new TreeNode(pre[0]);
    int index = -1; 
    // 找到在中序序列中根節點的位置
    for (int i = 0; i < tin.Length; i ++)
    {
        if(tin[i] == pre[0])
        {
            index = i;
            break;
        }
    }
    // 利用根節點的位置,再求出左右兩個子二叉樹的前序中序序列
    int[] subLeftPre = new int[index];
    int[] subLeftTin = new int[index];
    int[] subRightPre = new int[tin.Length - index - 1];
    int[] subRightTin = new int[tin.Length - index - 1];

    for (int j = 0; j < pre.Length; j ++)
    {
        if(j > 0)
        {
            if (j <= index)
            {
                subLeftPre[j - 1] = pre[j];
            }
            else
            {
                subRightPre[j - index - 1] = pre[j];
            }
        }
        if (j < index)
        {
            subLeftTin[j] = tin[j];
        }else if (j > index)
        {
            subRightTin[j - index - 1] = tin[j];
        }
    }
    if(index > 0)
    {
        root.left = reConstructBinaryTree(subLeftPre, subLeftTin);
    }
    if (tin.Length - index - 1 > 0)
    {
        root.right = reConstructBinaryTree(subRightPre, subRightTin);
    }
    return root;
}

解法2

上面的解法1,是根據解題思路最直觀的寫法,但是寫法很爛。由於C#對數組沒有取子數組的API。所以求子二叉樹的的前序中序序列寫的太複雜了,需要自己去new前序中序數組,再往這些數組中填寫正確的元素。後來是學習了別人的寫法進行了優化。不用自己去創建子二叉樹的前序中序數組,而是直接用父二叉樹的前序中序序列索引來表示就可以了。

實現代碼

public TreeNode reConstructSubTree(int[] pre, int p1, int p2, int[] tin, int t1, int t2)
{
    if (p1 <= p2 && t1 <= t2 && p2 >= 0 && t2 >= 0)
    {
        TreeNode root = new TreeNode(pre[p1]);
        for (int i = t1; i <= t2; i++)
        {
            if (tin[i] == pre[p1])
            {
                root.left = reConstructSubTree(pre, p1 + 1, p1 + i - t1, tin, t1, i - 1);
                root.right = reConstructSubTree(pre, p1 + i + 1 - t1, p2, tin, i + 1, t2);
                return root;
            }
        }
    }
    return null;
}

public TreeNode reConstructBinaryTreeOptimize(int[] pre, int[] tin)
{
    return reConstructSubTree(pre, 0, pre.Length - 1, tin, 0, tin.Length - 1);
}

遇到的問題

在使用解法1解這個問題的時候,一直只能通過80%多的測試用例,花了很久的時間去查爲什麼。問題原因是由於希望提高效率,所以就在一個循環中構建出子二叉樹的前序中序序列,但是邏輯上有bug,數組元素少賦值了,又由於新建的數組都是有默認值0的,導致序列實際上已經求解錯誤了,但又不易被發現。
遞歸問題是將大問題,轉換爲小問題,一直轉換爲小問題可以直接求解爲止。我的這個bug其實並不是不容易被發現,直接代入最小問題就可以發現的,比如前序序列爲{1, 2, 3},中序序列爲{2,1,3}。總結下來,以後對於遞歸問題,一定要先保證最小問題的解法是正確的,否則根基是錯誤的,最後得到的答案也一定是錯誤的。
最後放上有bug的代碼,注意是有bug的

public TreeNode reConstructBinaryTree(int[] pre, int[] tin)
        {
            if (!(pre.Length > 0))
            {
                return null;
            }
            TreeNode root = new TreeNode(pre[0]);
            int index = -1;
            for (int i = 0; i < tin.Length; i ++)
            {
                if(tin[i] == pre[0])
                {
                    index = i;
                    break;
                }
            }
            int[] subLeftPre = new int[index];
            int[] subLeftTin = new int[index];
            int[] subRightPre = new int[tin.Length - index - 1];
            int[] subRightTin = new int[tin.Length - index - 1];
			// bug 這裏j從1開始,會導致中序序列元素被少遍歷一次,導致少賦值
            for (int j = 1; j < pre.Length; j ++)
            {
                if (j <= index)
                {
                    subLeftPre[j - 1] = pre[j];
                }
                else
                {
                    subRightPre[j - index - 1] = pre[j];
                }
                if (j - 1 < index)
                {
                    subLeftTin[j - 1] = tin[j - 1];
                }else if (j - 1 > index)
                {
                    subRightTin[j - 2 - index] = tin[index + j - 1 - index];
                }
            }
            if(index > 0)
            {
                root.left = reConstructBinaryTree(subLeftPre, subLeftTin);
            }
            if (tin.Length - index - 1 > 0)
            {
                root.right = reConstructBinaryTree(subRightPre, subRightTin);
            }
            return root;
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章