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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

pat根据中序遍历和先序遍历_算法题399:从前序与中序遍历序列构造二叉树

發布時間:2024/9/19 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 pat根据中序遍历和先序遍历_算法题399:从前序与中序遍历序列构造二叉树 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

(給算法愛好者加星標,修煉編程內功)

來源:?數據結構和算法-山大王wld

問題描述

今天我們就不做關于雙指針的了,我們爬到樹上玩會兒,做一道關于二叉樹的題。今天的題就一句話,根據一棵樹的前序遍歷與中序遍歷構造二叉樹。

注意:

你可以假設樹中沒有重復的元素。

例如,給出

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

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

返回如下的二叉樹:

? ? 3

? ?/ \

? 9? 20

? ? /? \

? ?15? ?7

問題分析

做這題之前我們先來看一下樹的幾種遍歷順序。

先序遍歷:根節點→左子樹→右子樹。

中序遍歷:左子樹→根節點→右子樹。

后續遍歷:左子樹→右子樹→根節點。


其實也很好記,他是根據根節點遍歷的順序來定義的,比如先遍歷根節點就是先序遍歷,中間遍歷根節點就是中序遍歷,最后遍歷根節點就是后續遍歷,至于左子樹和右子樹哪個先遍歷,記住一點,這3種遍歷順序右節點永遠都不可能比左節點先遍歷。

我們就以上面的示例數據來看下,前序遍歷是[3,9,20,15,7],前序遍歷先訪問的是根節點,所以3就是根節點。中序遍歷是[9,3,15,20,7],由于中序遍歷是在左子樹都遍歷完的時候才遍歷根節點,所有在中序遍歷中3前面的都是3的左子樹節點,3后面的都是3的右子樹節點。也就是下面這樣

然后我們再使用同樣的方式對左右子樹繼續劃分,一直這樣下去,直到不能再分為止,我們來看下代碼

1public?TreeNode?buildTree(int[]?preorder,?int[]?inorder)?{
2????//把前序遍歷的值和中序遍歷的值放到list中
3????List?preorderList?=?new?ArrayList<>(); 4????List?inorderList?=?new?ArrayList<>(); 5????for?(int?i?=?0;?i? 6????????preorderList.add(preorder[i]); 7????????inorderList.add(inorder[i]); 8????} 9????return?helper(preorderList,?inorderList);10}1112private?TreeNode?helper(List?preorderList,?List?inorderList)?{13????if?(inorderList.size()?==?0)14????????return?null;15????//前序遍歷的第一個值就是根節點16????int?rootVal?=?preorderList.remove(0);17????//創建跟結點18????TreeNode?root?=?new?TreeNode(rootVal);19????//查看根節點在中序遍歷中的位置,然后再把中序遍歷的數組劈兩半,前面部分是20????//根節點左子樹的所有值,后面部分是根節點右子樹的所有值21????int?mid?=?inorderList.indexOf(rootVal);22????//[0,mid)是左子樹的所有值,inorderList.subList(0,?mid)表示截取inorderList23????//的值,截取的范圍是[0,mid),包含0不包含mid。24????root.left?=?helper(preorderList,?inorderList.subList(0,?mid));25????//[mid+1,inorderList.size())是右子樹的所有值,26????//?inorderList.subList(mid?+?1,?inorderList.size())表示截取inorderList27????//的值,截取的范圍是[mid+1,inorderList.size()),包含mid+1不包含inorderList.size()。28????root.right?=?helper(preorderList,?inorderList.subList(mid?+?1,?inorderList.size()));29????return?root;30}

上面代碼中是先把數組轉化為list集合,然后在list集合中進行截取,這樣效率明顯不是很高,實際上我們還可以不使用list,不對數組進行截取。

使用指針解決

我們只需要使用3個指針即可。一個是preStart,他表示的是前序遍歷開始的位置,一個是inStart,他表示的是中序遍歷開始的位置。一個是inEnd,他表示的是中序遍歷結束的位置,我們主要是對中序遍歷的數組進行拆解,下面就以下面的這棵樹來畫個圖分析下

他的前序遍歷是:[3,9,8,5,2,20,15,7]

他的中序遍歷是:[5,8,9,2,3,15,20,7]

這里只要找到了前序遍歷的結點在中序遍歷的位置,我們就可以把中序遍歷數組分解為兩部分了。如果index是前序遍歷的某個值在中序遍歷數組中的索引,以index為根節點劃分的話,那么中序遍歷中

[0,index-1]就是根節點左子樹的所有節點,

[index+1,inorder.length-1]就是根節點右子樹的所有節點。

中序遍歷好劃分,那么前序遍歷呢,如果是左子樹:

preStart=index+1;

如果是右子樹就稍微麻煩點,

preStart=preStart+(index-instart+1);

preStart是當前節點比如m先序遍歷開始的位置,index-instart+1就是當前節點m左子樹的數量加上當前節點的數量,所以preStart+(index-instart+1)就是當前節點m右子樹前序遍歷開始的位置,我們來看下完整代碼

1public?TreeNode?buildTree(int[]?preorder,?int[]?inorder)?{
2????return?helper(0,?0,?inorder.length?-?1,?preorder,?inorder);
3}
4
5public?TreeNode?helper(int?preStart,?int?inStart,?int?inEnd,?int[]?preorder,?int[]?inorder)?{
6????if?(preStart?>?preorder.length?-?1?||?inStart?>?inEnd)?{
7????????return?null;
8????}
9????//創建結點
10????TreeNode?root?=?new?TreeNode(preorder[preStart]);
11????int?index?=?0;
12????//找到當前節點root在中序遍歷中的位置,然后再把數組分兩半
13????for?(int?i?=?inStart;?i?<=?inEnd;?i++)?{
14????????if?(inorder[i]?==?root.val)?{
15????????????index?=?i;
16????????????break;
17????????}
18????}
19????root.left?=?helper(preStart?+?1,?inStart,?index?-?1,?preorder,?inorder);
20????root.right?=?helper(preStart?+?index?-?inStart?+?1,?index?+?1,?inEnd,?preorder,?inorder);
21????return?root;
22}

使用棧解決

如果使用棧來解決首先要搞懂一個知識點,就是前序遍歷挨著的兩個值比如m和n,他們會有下面兩種情況之一的關系。

1,n是m左子樹節點的值。

2,n是m右子樹節點的值或者是m某個祖先節點的右節點的值。

  • 對于第一個知識點我們很容易理解,如果m的左子樹不為空,那么n就是m左子樹節點的值。

  • 對于第二個問題,如果一個結點沒有左子樹只有右子樹,那么n就是m右子樹節點的值,如果一個結點既沒有左子樹也沒有右子樹,那么n就是m某個祖先節點的右節點,我們只要找到這個祖先節點就好辦了。

搞懂了這點,代碼就很容易寫了,下面看下完整代碼

1public?TreeNode?buildTree(int[]?preorder,?int[]?inorder)?{
2????if?(preorder.length?==?0)
3????????return?null;
4????Stack?s?=?new?Stack<>(); 5????//前序的第一個其實就是根節點 6????TreeNode?root?=?new?TreeNode(preorder[0]); 7????TreeNode?cur?=?root; 8????for?(int?i?=?1,?j?=?0;?i? 9????????//第一種情況10????????if?(cur.val?!=?inorder[j])?{11????????????cur.left?=?new?TreeNode(preorder[i]);12????????????s.push(cur);13????????????cur?=?cur.left;14????????}?else?{15????????????//第二種情況16????????????j++;17????????????//找到合適的cur,然后確定他的右節點18????????????while?(!s.empty()?&&?s.peek().val?==?inorder[j])?{19????????????????cur?=?s.pop();20????????????????j++;21????????????}22????????????//給cur添加右節點23????????????cur?=?cur.right?=?new?TreeNode(preorder[i]);24????????}25????}26????return?root;27}

總結

這題如果直接在紙上推算出來還是很簡單的,如果寫成代碼就稍微有一點難度。當然第一種寫法還是非常簡單,他是每次遍歷都會把數組截取,但截取效率不高,所以第二種方式就使用指針的方式,每次遍歷的時候通過指針來固定左子樹和右子樹在數組中的范圍。第3種方式是巧妙的運用了前序遍歷的特點,然后使用棧的方式解決,這種方式也是非常經典的,一般不太容易想到。

- EOF -

推薦閱讀??點擊標題可跳轉

1、算法題400:二叉樹的鋸齒形層次遍歷

2、算法題367:二叉樹的最大深度

3、完全二叉樹的節點數,你真的會算嗎?

覺得本文有幫助?請分享給更多人

關注「算法愛好者」加星標,修煉編程內功

好文章,我在看??

總結

以上是生活随笔為你收集整理的pat根据中序遍历和先序遍历_算法题399:从前序与中序遍历序列构造二叉树的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。