題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不包含重復的數字。
例如:輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建如下圖的二叉樹并輸出它的頭節點。
二叉樹節點的定義如下
struct BinaryTreeNode {
int m_nValue;
BinaryTreeNode *m_pLeft;
BinaryTreeNode *m_pRight;
};
二叉樹的遍歷流程
通常樹有如下幾種遍歷方式:
前序遍歷:先訪問根結點,再訪問左子結點,最后訪問右子結點。
中序遍歷:先訪問左子結點,再訪問根結點,最后訪問右子結點。
后序遍歷:先訪問左子結點,再訪問右子結點,最后訪問根結點。
注意:
前序和后序的遍歷序列的無法唯一確定一顆二叉樹。
分析
在二叉樹的前序遍歷中,第一個數字總是樹的根節點的值,但在中序遍歷序列中,根節點的值在序列的中間,左子樹的節點的值位于根節點的值的左邊,而右子樹的節點的值位于根節點的值的右邊。因此我們需要掃描中序遍歷序列,才能找到根節點的值
前序遍歷序列的第一個數字1就是根節點的值。掃描中序遍歷序列,就能確定根節點的值的位置。根據中序遍歷的特點,在根節點的值1前面的3個數字都是左子樹節點的值,位于1后面的數字都是右子樹節點的值。
由于在中序遍歷序列中,有3個數字是左子樹節點的值,因此左子樹共有3個左子節點。同樣,在前序遍歷序列中,根節點后面的3個數字就是3個左子樹節點的值,再后面的所有數字都是右子樹節點的值,這樣我們就在前序遍歷和中序遍歷中分別找到了左、右子樹對應的子序列。
既然我們已經分別找到了左、右子樹的前序遍歷序列和中序遍歷序列,我們可以用同樣的方法分別構建左、右子樹。
思路總結:
先根據前序遍歷序列的第一個數字創建根結點,接下來在中序遍歷序列中找到根結點的位置,這樣就能確定左、右子樹結點的數量。在前序遍歷和中序遍歷的序列中劃分了左、右子樹結點的值之后,就可以遞歸地去分別構建它的左右子樹。
遞歸實現(C++)
// 前向聲明
BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder);
BinaryTreeNode* Construct(int *preorder, int *inorder, int length)
{
// 確保輸入的數組是有值的,處理為空的情況
if (preorder == nullptr || inorder == nullptr || length <= 0)
return nullptr;
return ConstructCore(preorder, preorder + length - 1, inorder, inorder + length - 1);
}
// startPreorder 開始前序遍歷指針 startInorder 開始的中序遍歷指針
BinaryTreeNode* ConstructCore(int *startPreorder, int *endPreorder, int *startInorder, int *endInorder )
{
// 前序遍歷的第一個值就是根節點
int rootValue = startPreorder[0];
// 創建一個新的二叉樹
BinaryTreeNode *root = new BinaryTreeNode();
// 值為根節點的值
root->m_nValue = rootValue;
// 左右子樹目前為空
root->m_pLeft = root->m_pRight = nullptr;
// 處理只有根節點的情況
if (startPreorder == endPreorder) { // 如果前序遍歷序列的開始指針等于結尾指針
if (startInorder == endInorder) // 中序遍歷序列的開始指針等于結束指針,那么只有根節點,直接返回
return root;
else
{
logic_error ex("Invalid Input");
throw exception(ex);
}
}
// 在中序遍歷序列中找到根節點的值
int *rootInorder = startInorder;
while (rootInorder<=endInorder && *rootInorder != rootValue)
++rootInorder;
// 中序遍歷結束,找不到根節點,輸入中序序列錯誤,拋出異常
if (rootInorder == endInorder && *rootInorder != rootValue)
{
logic_error ex("Invalid Input");
throw exception(ex);
}
// 找到了根節點,就可以確認左子樹和右子樹的范圍
int leftLength = rootInorder - startInorder;
int *leftPreorderEnd = startPreorder + leftLength;
if (leftLength > 0)
{
// 構建左子樹
root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);
}
if (leftLength < endPreorder - startPreorder)
{
// 構建右子樹
root->m_pRight = ConstructCore(leftPreorderEnd + 1, endInorder, rootInorder + 1, endInorder);
}
return root;
}