日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

根据中序、前序遍历重建二叉树

發(fā)布時(shí)間:2023/12/13 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 根据中序、前序遍历重建二叉树 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 題目
  • 遞歸
    • 思路
    • 細(xì)節(jié)
    • 易錯(cuò)
    • 代碼
    • 復(fù)雜度分析
  • 迭代
    • 思路
    • 細(xì)節(jié)
    • 易錯(cuò)
    • 代碼
    • 復(fù)雜度分析


題目

輸入某二叉樹的前序遍歷和中序遍歷的結(jié)果,請重建該二叉樹。假設(shè)輸入的前序遍歷和中序遍歷的結(jié)果中都不含重復(fù)的數(shù)字。

例如,給出

前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]

返回如下的二叉樹:

限制:

0 <= 節(jié)點(diǎn)個(gè)數(shù) <= 5000


遞歸

思路

首先要明確最重要的一個(gè)知識(shí):

對于任意一顆樹而言,前序遍歷的形式總是

[ 根節(jié)點(diǎn), [左子樹的前序遍歷結(jié)果], [右子樹的前序遍歷結(jié)果] ]

即根節(jié)點(diǎn)總是前序遍歷中的第一個(gè)節(jié)點(diǎn)。而中序遍歷的形式總是

[ [左子樹的中序遍歷結(jié)果], 根節(jié)點(diǎn), [右子樹的中序遍歷結(jié)果] ]

顯然:

  • 對前序遍歷來講,找到左右子樹的遍歷結(jié)果分界線是困難的,找到根節(jié)點(diǎn)是簡單的
  • 對中序遍歷來講,找到根節(jié)點(diǎn)是困難的,但找到根節(jié)點(diǎn)之后,左右兩側(cè)自然分成左右兩棵子樹

根據(jù)上面的特性,我們可以做出互補(bǔ):

  • 通過前序遍歷的結(jié)果數(shù)組的首元素確定根節(jié)點(diǎn)
  • 根據(jù)找到的根節(jié)點(diǎn)結(jié)合中序遍歷數(shù)組確定左右子樹的節(jié)點(diǎn)數(shù)目
  • 重復(fù)上述過程,我們也就可以通過將每個(gè)節(jié)點(diǎn)視作根節(jié)點(diǎn),不斷遞歸生成左右子樹,無法再生成左右子樹。很顯然生成左右子樹的過程可以用遞歸思想來實(shí)現(xiàn)。


    細(xì)節(jié)

    思路有了,仍需解決幾個(gè)問題:

  • 即使通過前序遍歷找到根節(jié)點(diǎn),怎樣確定根節(jié)點(diǎn)在中序遍歷中的位置?
  • 遞歸生成左右子樹的細(xì)節(jié)操作是什么?
  • 先解決第一個(gè)問題:

    普通的方法當(dāng)然是拿著根節(jié)點(diǎn)的值,從中序遍歷結(jié)果數(shù)組inorder [0]開始遍歷,但是每次在生成根節(jié)點(diǎn)時(shí)都進(jìn)行遍歷的話,時(shí)間復(fù)雜度較高(O(N))。因此可以使用哈希表來建設(shè)中序遍歷數(shù)組值到下標(biāo)鍵值對映射

    在構(gòu)造二叉樹的過程之前,我們可以對中序遍歷的列表進(jìn)行一遍掃描(O(N)),就可以構(gòu)造出這個(gè)哈希映射。

    在此后構(gòu)造二叉樹的過程中,我們就只需要 O(1)的時(shí)間對根節(jié)點(diǎn)進(jìn)行定位了。(一次O(N),N次O(1));否則我們必須每次都遍歷一遍中序遍歷結(jié)果數(shù)組定位根節(jié)點(diǎn)(N次O(N))。

    再來說第二個(gè)問題:

    遞歸生成左右子樹這種說法聽起來還是太“模糊”了,其實(shí)我們實(shí)際做的操作是不停的生成根節(jié)點(diǎn),再進(jìn)入這個(gè)根節(jié)點(diǎn)的左右子樹,在每個(gè)子樹中生成當(dāng)前子樹的根節(jié)點(diǎn),直到這個(gè)”根節(jié)點(diǎn)“沒有子樹為止。

    易錯(cuò)

    寫代碼的時(shí)候沒有子樹應(yīng)該返回空指針——return nullptr;,粗心大意寫成了return null;,null和nullptr是有區(qū)別的。

    代碼

    class Solution { private:map<int, int> index; // 映射值給定值對應(yīng)的下標(biāo) public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int num = inorder.size();for (int i = 0; i < num; i++){index[inorder[i]] = i; // 建立中序遍歷數(shù)組 值到下標(biāo) 的鍵值對映射,快速定位根節(jié)點(diǎn)}return buildRoot(preorder, inorder, 0, num - 1, 0, num - 1);}// 把每個(gè)節(jié)點(diǎn)都當(dāng)作它自身的“根節(jié)點(diǎn)”,進(jìn)入到每個(gè)節(jié)點(diǎn)遍歷生成它的左右子樹、及根節(jié)點(diǎn)本身 TreeNode* buildRoot(vector<int>& pre, vector<int>& in, int pre_left, int pre_right, int in_left, int in_right) {if (pre_left > pre_right) { // 沒有子樹return nullptr;}int pre_root = pre_left; // 前序遍歷的根節(jié)點(diǎn)就是左邊界int in_root = index[pre[pre_left]]; // 根據(jù)映射關(guān)系確定中序遍歷中的根節(jié)點(diǎn)TreeNode* root = new TreeNode(in[in_root]); // 建立根節(jié)點(diǎn)// 等價(jià)于TreeNode root = TreeNode(in[in_root]);// TreeNode *proot = &root;// 但沒必要這樣寫,可能便于理解但是過于繁瑣int lefttree_num = in_root - in_left; // 確定左子樹中節(jié)點(diǎn)數(shù)目// 前序遍歷 根節(jié)點(diǎn)(左邊界)+1 到 根節(jié)點(diǎn)+左子樹數(shù)量 的范圍為左子樹// 中序遍歷根節(jié)點(diǎn)左側(cè)為左子樹root->left = buildRoot(pre, in, pre_left + 1, pre_left + lefttree_num, in_left, in_root - 1); // 前序遍歷 根節(jié)點(diǎn)+左子樹數(shù)量+1 到 右邊界 的范圍為右子樹// 中序遍歷根節(jié)點(diǎn)右側(cè)為右子樹root->right = buildRoot(pre, in, pre_left + 1 + lefttree_num, pre_right, in_root + 1, in_right);return root;} };

    復(fù)雜度分析

    時(shí)間復(fù)雜度:O(n),其中 n 是樹中的節(jié)點(diǎn)個(gè)數(shù)。

    空間復(fù)雜度:O(n),除去返回的答案需要的 O(n)空間之外,我們還需要使用 O(n) 的空間存儲(chǔ)哈希映射,以及 O(h)(其中 h 是樹的高度)的空間表示遞歸時(shí)棧空間。這里 h < n,所以總空間復(fù)雜度為 O(n)。



    迭代

    思路

    前序遍歷的相鄰節(jié)點(diǎn) u 和 v 有如下關(guān)系:

  • v 是 u 的左兒子;
  • u 沒有左兒子。則 v 是 u 或者 u 祖先節(jié)點(diǎn)的右兒子。
  • 以此樹為例:

    它的前序遍歷和中序遍歷分別為

    preorder = [3, 9, 8, 5, 4, 10, 20, 15, 7]
    inorder = [4, 5, 8, 10, 9, 3, 15, 20, 7]

    可以看到,對于3,9,8,5,4它們之間滿足第一種關(guān)系(例如:8是9的左兒子),對于4,10它們滿足第二種關(guān)系,10是4祖父節(jié)點(diǎn)的右兒子。

    也就是前序遍歷會(huì)

  • 從根節(jié)點(diǎn)開始,一直遍歷左子樹
  • 直到左子樹遍歷完了,開始遍歷右子樹
  • 如果當(dāng)前的節(jié)點(diǎn)沒有右子樹,則會(huì)回溯遍歷祖先節(jié)點(diǎn)的右子樹。
  • 那么我們可以根據(jù)這一特性,我們可以用一個(gè)棧來存儲(chǔ)祖先節(jié)點(diǎn)和左子樹,直到左子樹被遍歷完,(本例中,將3,9,8,5,4依次入棧,直到遇到10)此時(shí)開始尋找當(dāng)前節(jié)點(diǎn)(10)是誰的右兒子。

    細(xì)節(jié)

    思路有了,仍需解決幾個(gè)問題:

  • 當(dāng)開始遍歷右子樹,怎么確定當(dāng)前節(jié)點(diǎn)是誰的右兒子呢?
  • 這時(shí)來看中序遍歷,我們可以發(fā)現(xiàn),中序遍歷結(jié)果數(shù)組的首元素是——根節(jié)點(diǎn)不斷往左走達(dá)到的最終節(jié)點(diǎn)。 根據(jù)這一特性,我們可以創(chuàng)建一個(gè)指針 index 指向當(dāng)前的最左子樹

    首先我們將根節(jié)點(diǎn) 3 入棧,再初始化 index 指向的節(jié)點(diǎn)為 4,隨后對于前序遍歷中的每個(gè)節(jié)點(diǎn),我們依次判斷它是棧頂節(jié)點(diǎn)的左兒子,還是棧中某個(gè)節(jié)點(diǎn)的右兒子。

  • 我們遍歷 9。9 一定是棧頂節(jié)點(diǎn) 3 的左兒子。我們使用反證法,假設(shè) 9 是 3 的右兒子,那么 3 沒有左兒子,index 應(yīng)該恰好指向 3,但實(shí)際上為 4,因此產(chǎn)生了矛盾。所以我們將 9 作為 3 的左兒子,并將 9 入棧。
  • stack = [3, 9]
    index -> inorder[0] = 4

  • 我們遍歷 8,5 和 4。同理可得它們都是上一個(gè)節(jié)點(diǎn)(棧頂節(jié)點(diǎn))的左兒子,所以它們會(huì)依次入棧。
  • stack = [3, 9, 8, 5, 4]
    index -> inorder[0] = 4

  • 當(dāng)我們遍歷到 10,這時(shí)情況就不一樣了。我們發(fā)現(xiàn)此時(shí) index 指向的節(jié)點(diǎn)和當(dāng)前的棧頂節(jié)點(diǎn)一樣,都為 4,也就是說 4 沒有左兒子,那么 10 必須為棧中某個(gè)節(jié)點(diǎn)的右兒子。 那么如何找到這個(gè)節(jié)點(diǎn)呢?棧中的節(jié)點(diǎn)的順序和它們在前序遍歷中出現(xiàn)的順序是一致的,而且每一個(gè)節(jié)點(diǎn)的右兒子都還沒有被遍歷過, 那么這些節(jié)點(diǎn)的順序和它們在中序遍歷中出現(xiàn)的順序一定是相反的(原因如下)。
  • 這是因?yàn)闂V械娜我鈨蓚€(gè)相鄰的節(jié)點(diǎn),前者都是后者的某個(gè)祖先。并且我們知道,棧中的任意一個(gè)節(jié)點(diǎn)的右兒子還沒有被遍歷過(前序遍歷順序——中左右),說明后者一定是前者左兒子,那么后者就先于前者出現(xiàn)在中序遍歷中(中序遍歷順序——左中右)。

    因此我們可以先把此時(shí)的棧頂元素保存并彈出, 然后把 index 不斷向右移動(dòng),并與棧頂節(jié)點(diǎn)進(jìn)行比較。如果 index 對應(yīng)的元素恰好等于棧頂節(jié)點(diǎn),那么說明上一個(gè)被彈出的節(jié)點(diǎn)沒有右子樹,且其本身是當(dāng)前節(jié)點(diǎn)的左子樹, 所以重復(fù)將棧頂節(jié)點(diǎn)保存并彈出,然后將 index 增加 1 的過程,直到 index 對應(yīng)的元素不等于棧頂節(jié)點(diǎn),此時(shí) index 對應(yīng)的元素就是上一個(gè)被保存且彈出的棧頂節(jié)點(diǎn)的右子樹。按照這樣的過程,我們彈出的最后一個(gè)節(jié)點(diǎn) x 就是 10 的父節(jié)點(diǎn),這是因?yàn)?10 出現(xiàn)在了 x 與 x在棧中的下一個(gè)節(jié)點(diǎn)的中序遍歷之間,因此 10 就是 x 的右兒子(根據(jù)中序遍歷順序——左中右,x是中,10是右,x在棧中的下一個(gè)節(jié)點(diǎn)是x的父節(jié)點(diǎn))。

    回到我們的例子,我們會(huì)依次從棧頂彈出 4,5 和 8,并且將 index 向右移動(dòng)了三次。我們將 10 作為最后彈出的節(jié)點(diǎn) 8 的右兒子,并將 10 入棧。

    stack = [3, 9, 10]
    index -> inorder[3] = 10

  • 我們遍歷 20時(shí)。index 恰好指向當(dāng)前棧頂節(jié)點(diǎn) 10,那么我們會(huì)依次從棧頂彈出 10,9 和 3,并且將 index 向右移動(dòng)了三次。我們將 20 作為最后彈出的節(jié)點(diǎn) 3 的右兒子,并將 20 入棧。
  • stack = [20]
    index -> inorder[6] = 15

  • 我們遍歷 15,將 15 作為棧頂節(jié)點(diǎn) 20 的左兒子,并將 15 入棧。
  • stack = [20, 15]
    index -> inorder[6] = 15

  • 我們遍歷 7。index 恰好指向當(dāng)前棧頂節(jié)點(diǎn) 15,那么我們會(huì)依次從棧頂彈出 15 和 20,并且將 index 向右移動(dòng)了兩次。我們將 7 作為最后彈出的節(jié)點(diǎn) 20 的右兒子,并將 7 入棧。
  • stack = [7]
    index -> inorder[8] = 7

    此時(shí)遍歷結(jié)束,我們構(gòu)造出了正確的二叉樹。

    總結(jié)來講就是,遍歷前序遍歷結(jié)果數(shù)組并將其壓到棧中:

  • 當(dāng)棧頂元素不等于index指向的元素時(shí),將當(dāng)前元素作為棧頂元素的左兒子,然后當(dāng)前元素入棧成為新棧頂
  • 當(dāng)棧頂元素等于index指向的元素時(shí),彈出并保存棧頂元素,同時(shí)將index遞增1,再判斷棧頂元素和index指向的元素之間的關(guān)系,相等則重復(fù)上述操作,不相等則將當(dāng)前元素作為最后一個(gè)被彈出的棧頂元素的右兒子,然后將當(dāng)前元素入棧成為新棧頂
  • 易錯(cuò)

  • 通過判斷前序遍歷或中序遍歷的結(jié)果數(shù)組是否為空,來確定二叉樹是否為空。
  • 二叉樹不為空時(shí),在建立左右子樹的循環(huán)操作之前,先將根節(jié)點(diǎn)入棧。因?yàn)楦?jié)點(diǎn)的建立操作與其他左右子樹不同,放到循環(huán)里面要單獨(dú)處理,反而繁瑣。
  • 注意保存彈出的棧頂元素。
  • 在生成右子樹的時(shí)候,棧不為空也應(yīng)該是重要的循環(huán)判定條件之一。
  • 代碼

    class Solution2 { // 迭代 public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {if (!preorder.size()) {return nullptr;}stack<TreeNode*> st;TreeNode* root = new TreeNode(preorder[0]); // 建立根節(jié)點(diǎn)st.push(root); // 根節(jié)點(diǎn)入棧// 否則無法進(jìn)行將節(jié)點(diǎn)歸為左兒子或者右兒子的操作// 因?yàn)檫M(jìn)行上面的操作需要訪問棧頂元素的left或者rightint index = 0;for (size_t i = 1; i < preorder.size(); i++) {int pre = preorder[i];int in = inorder[index];auto node = st.top();if (node->val != in) { // 如果前序遍歷i位置的數(shù)和中序遍歷index位置的數(shù)不相等// 說明i位置的數(shù)是二叉樹的左子樹node->left = new TreeNode(pre);st.push(node->left);}else {while (!st.empty() && in == st.top()->val) {in = inorder[++index];node = st.top(); // 保存彈出的節(jié)點(diǎn)// 當(dāng)跳出while時(shí),pre的值即為該節(jié)點(diǎn)右子樹st.pop();}node->right = new TreeNode(pre);st.push(node->right);}}return root;} };

    復(fù)雜度分析

    時(shí)間復(fù)雜度:O(n),其中 n 是樹中的節(jié)點(diǎn)個(gè)數(shù)。

    空間復(fù)雜度:O(n),除去返回的答案需要的 O(n) 空間之外,我們還需要使用 O(h)(其中 h 是樹的高度)的空間存儲(chǔ)棧。這里 h < n,所以(在最壞情況下)總空間復(fù)雜度為 O(n)。

    總結(jié)

    以上是生活随笔為你收集整理的根据中序、前序遍历重建二叉树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。