C++構(gòu)建二叉樹(shù)問(wèn)題

2023-09-20 09:23 更新

Question

給定一個(gè)二叉樹(shù)的前序遍歷 preorder 和中序遍歷 inorder ,請(qǐng)從中構(gòu)建二叉樹(shù),返回二叉樹(shù)的根節(jié)點(diǎn)。

構(gòu)建二叉樹(shù)的示例數(shù)據(jù)

圖 12-5   構(gòu)建二叉樹(shù)的示例數(shù)據(jù)

判斷是否為分治問(wèn)題

原問(wèn)題定義為從 preorderinorder 構(gòu)建二叉樹(shù),其是一個(gè)典型的分治問(wèn)題。

  • 問(wèn)題可以被分解:從分治的角度切入,我們可以將原問(wèn)題劃分為兩個(gè)子問(wèn)題:構(gòu)建左子樹(shù)、構(gòu)建右子樹(shù),加上一步操作:初始化根節(jié)點(diǎn)。而對(duì)于每個(gè)子樹(shù)(子問(wèn)題),我們?nèi)匀豢梢詮?fù)用以上劃分方法,將其劃分為更小的子樹(shù)(子問(wèn)題),直至達(dá)到最小子問(wèn)題(空子樹(shù))時(shí)終止。
  • 子問(wèn)題是獨(dú)立的:左子樹(shù)和右子樹(shù)是相互獨(dú)立的,它們之間沒(méi)有交集。在構(gòu)建左子樹(shù)時(shí),我們只需要關(guān)注中序遍歷和前序遍歷中與左子樹(shù)對(duì)應(yīng)的部分。右子樹(shù)同理。
  • 子問(wèn)題的解可以合并:一旦得到了左子樹(shù)和右子樹(shù)(子問(wèn)題的解),我們就可以將它們鏈接到根節(jié)點(diǎn)上,得到原問(wèn)題的解。

如何劃分子樹(shù)

根據(jù)以上分析,這道題是可以使用分治來(lái)求解的,但如何通過(guò)前序遍歷 preorder 和中序遍歷 inorder 來(lái)劃分左子樹(shù)和右子樹(shù)呢?

根據(jù)定義,preorderinorder 都可以被劃分為三個(gè)部分。

  • 前序遍歷:[ 根節(jié)點(diǎn) | 左子樹(shù) | 右子樹(shù) ] ,例如圖 12-5 的樹(shù)對(duì)應(yīng) [ 3 | 9 | 2 1 7 ]
  • 中序遍歷:[ 左子樹(shù) | 根節(jié)點(diǎn) | 右子樹(shù) ] ,例如圖 12-5 的樹(shù)對(duì)應(yīng) [ 9 | 3 | 1 2 7 ] 。

以上圖數(shù)據(jù)為例,我們可以通過(guò)圖 12-6 所示的步驟得到劃分結(jié)果。

  1. 前序遍歷的首元素 3 是根節(jié)點(diǎn)的值。
  2. 查找根節(jié)點(diǎn) 3 在 inorder 中的索引,利用該索引可將 inorder 劃分為 [ 9 | 3 | 1 2 7 ] 。
  3. 根據(jù) inorder 劃分結(jié)果,易得左子樹(shù)和右子樹(shù)的節(jié)點(diǎn)數(shù)量分別為 1 和 3 ,從而可將 preorder 劃分為 [ 3 | 9 | 2 1 7 ]

在前序和中序遍歷中劃分子樹(shù)

圖 12-6   在前序和中序遍歷中劃分子樹(shù)

基于變量描述子樹(shù)區(qū)間

根據(jù)以上劃分方法,我們已經(jīng)得到根節(jié)點(diǎn)、左子樹(shù)、右子樹(shù)在 preorderinorder 中的索引區(qū)間。而為了描述這些索引區(qū)間,我們需要借助幾個(gè)指針變量。

  • 將當(dāng)前樹(shù)的根節(jié)點(diǎn)在 preorder 中的索引記為 i 。
  • 將當(dāng)前樹(shù)的根節(jié)點(diǎn)在 inorder 中的索引記為 m 。
  • 將當(dāng)前樹(shù)在 inorder 中的索引區(qū)間記為 [l,r] 。

如表 12-1 所示,通過(guò)以上變量即可表示根節(jié)點(diǎn)在 preorder 中的索引,以及子樹(shù)在 inorder 中的索引區(qū)間。

表 12-1   根節(jié)點(diǎn)和子樹(shù)在前序和中序遍歷中的索引

根節(jié)點(diǎn)在 preorder 中的索引 子樹(shù)在 inorder 中的索引區(qū)間
當(dāng)前樹(shù) i [l,r]
左子樹(shù) i+1 [l,m?1]
右子樹(shù) i+1+(m?l) [m+1,r]

請(qǐng)注意,右子樹(shù)根節(jié)點(diǎn)索引中的 (m?l) 的含義是“左子樹(shù)的節(jié)點(diǎn)數(shù)量”,建議配合圖 12-7 理解。

根節(jié)點(diǎn)和左右子樹(shù)的索引區(qū)間表示

圖 12-7   根節(jié)點(diǎn)和左右子樹(shù)的索引區(qū)間表示

4.   代碼實(shí)現(xiàn)?

為了提升查詢 m 的效率,我們借助一個(gè)哈希表 hmap 來(lái)存儲(chǔ)數(shù)組 inorder 中元素到索引的映射。

build_tree.cpp

/* 構(gòu)建二叉樹(shù):分治 */
TreeNode *dfs(vector<int> &preorder, unordered_map<int, int> &inorderMap, int i, int l, int r) {
    // 子樹(shù)區(qū)間為空時(shí)終止
    if (r - l < 0)
        return NULL;
    // 初始化根節(jié)點(diǎn)
    TreeNode *root = new TreeNode(preorder[i]);
    // 查詢 m ,從而劃分左右子樹(shù)
    int m = inorderMap[preorder[i]];
    // 子問(wèn)題:構(gòu)建左子樹(shù)
    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1);
    // 子問(wèn)題:構(gòu)建右子樹(shù)
    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);
    // 返回根節(jié)點(diǎn)
    return root;
}

/* 構(gòu)建二叉樹(shù) */
TreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {
    // 初始化哈希表,存儲(chǔ) inorder 元素到索引的映射
    unordered_map<int, int> inorderMap;
    for (int i = 0; i < inorder.size(); i++) {
        inorderMap[inorder[i]] = i;
    }
    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);
    return root;
}

圖 12-8 展示了構(gòu)建二叉樹(shù)的遞歸過(guò)程,各個(gè)節(jié)點(diǎn)是在向下“遞”的過(guò)程中建立的,而各條邊(即引用)是在向上“歸”的過(guò)程中建立的。

構(gòu)建二叉樹(shù)的遞歸過(guò)程

built_tree_step2

built_tree_step3

built_tree_step4

built_tree_step5

built_tree_step6

built_tree_step7

built_tree_step8

built_tree_step9


圖 12-8   構(gòu)建二叉樹(shù)的遞歸過(guò)程

每個(gè)遞歸函數(shù)內(nèi)的前序遍歷 preorder 和中序遍歷 inorder 的劃分結(jié)果如圖 12-9 所示。

每個(gè)遞歸函數(shù)中的劃分結(jié)果

圖 12-9   每個(gè)遞歸函數(shù)中的劃分結(jié)果

設(shè)樹(shù)的節(jié)點(diǎn)數(shù)量為 n ,初始化每一個(gè)節(jié)點(diǎn)(執(zhí)行一個(gè)遞歸函數(shù) dfs() )使用 O(1) 時(shí)間。因此總體時(shí)間復(fù)雜度為 O(n) 。

哈希表存儲(chǔ) inorder 元素到索引的映射,空間復(fù)雜度為 O(n) 。最差情況下,即二叉樹(shù)退化為鏈表時(shí),遞歸深度達(dá)到 n ,使用 O(n) 的棧幀空間。因此總體空間復(fù)雜度為 O(n) 。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)