注意可以從
前序與中序遍歷中重構二叉樹或中序遍歷與後序遍歷中重構二叉樹
首先,我們要明白二叉樹的遍歷方式爲前 中 後 與層序遍歷
,其中前三種爲dfs
。
- 前序遍歷爲
根節點 ----->左子樹----->右子樹的順序
- 中序遍歷爲
左子樹----->根節點----->右子樹的順序
- 後序遍歷爲
左子樹----->右子樹------>根的順序
- 只要給出的遍歷中有中序遍歷,我們就可以推出在根節點的左側爲左子樹,在根節點的右面爲右子樹
- 我們要明確的是如何確定根接點,在前序遍歷中
序列的第一個值爲根節點
,在後序遍歷中序列的最後一個值爲根節點
,通過在前後序遍歷中確定的根節點的值再在中序遍歷中確定根的位置,則在找到根的前面爲左子樹的值,在根的後面爲右子樹的值 - 根據左子樹的節點的數量,
middle-left
,則可在`後序與前序遍歷中確定左子樹與右子樹在序列中的位置(注意是根據元素的個數確定) - 從而可以轉換爲分治算法與遞歸接着處理
package JDFS;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/4/29 0029 17:12
* 從前序與中序序列中構造二叉樹
* https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/si-lu-qing-xi-dai-ma-jian-ji-he-105ti-si-lu-yi-z-2/
*/
public class Problem105 {
//遞歸:
// 在中序遍歷數組中位於其左邊的一定是在坐下較
//根據畫圖可以看出
//固定住前序遍裏數組中的元素,在中序遍歷數組中進行查找
//則位於 中序遍歷數組左側的一定是其 root節點的左子樹,位於其右側的一定是柚子樹
/**
* preOrder第一個元素爲root,在inorder裏面找到root,
* 在它之前的左子樹(長爲l1),之後右子樹(l2),preOrder[1]到perOrder[l1]爲左子樹
* 之後爲右子樹分別遞歸哦
*
*/
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length==0&&inorder.length==0) return null;
return dfs(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
/**
*
* @param preOrder
* @param inorder
* @param inOrderleft
* @param inOrderright
* @param preOrderleft
* @param preOrderright
* @return
* 如今我們已經遇到了好幾個類似的從數組重建二叉樹的題目了,先來看幾個關鍵詞:遞歸、區間分治
*
* 很早之前我們說過,這種區間分治的題目,使用左閉右閉區間更加方便,如果堅持使用左閉右開區間,容易導致人懵逼。
* 在這個題目中也是類似的,我們使用左閉右閉區間。
重建二叉樹的基本思路就是先構造根節點,再構造**左子樹**,接下來構造**右子樹**,其中,
構造**左子樹**和**右子樹**是一個子問題,遞歸處理即可。因此我們只關心如何構造根節點,以及如何遞歸構造左子樹和右子樹。
*遞歸函數的設計上,仍舊採用**左閉右閉**對數組局部進行描述。即一個數組,使用 3 個變量描述:
數組本身 arr
數組的起始位置 lo
數組的結束位置 hi
public TreeNode dfs(int[] preOrder,int[] inorder,int inOrderleft,int inOrderright,int preOrderleft,int preOrderright){
它的主要參數相比原來題目中的原型多了數組範圍描述變量,這是我們所期望的,因爲我們要做區間分治嘛。
*後面的故事就很簡單了,三元組 (preorder, lo1, hi1) 描述的前序遍歷數組,以及三元組 (inorder, lo2, hi2) 描述的中序遍歷數組,如何從它們重建二叉樹?遞歸的說法就是:
你會發現,遞歸創建左子樹,
無非就是再構造一個新的前序遍歷的三元組 (preorder, lo1+1, lo1+mid-lo2)
以及 (inorder, lo2, mid-1),其中 mid 是當前 inorder 中 root 的位置。
看一下前序和中序有什麼特點,前序1,2,4,7,3,5,6,8 ,中序4,7,2,1,5,3,8,6;
有如下特徵:
1、前序中左起第一位1肯定是根結點,我們可以據此找到中序中根結點的位置rootin;
2、中序中根結點左邊就是左子樹結點,右邊就是右子樹結點,即[左子樹結點,根結點,右子樹結點],
我們就可以得出左子樹結點個數爲int left = rootin - leftin;;
3、前序中結點分佈應該是:[根結點,左子樹結點,右子樹結點];
4、根據前一步確定的左子樹個數,可以確定前序中左子樹結點和右子樹結點的範圍;
5、如果我們要前序遍歷生成二叉樹的話,下一層遞歸應該是:
左子樹:root->left = pre_order(前序左子樹範圍,中序左子樹範圍,前序序列,中序序列);;
右子樹:root->right = pre_order(前序右子樹範圍,中序右子樹範圍,前序序列,中序序列);。
6、每一層遞歸都要返回當前根結點root;
*/
public TreeNode dfs(int[] preOrder,int[] inorder,int preOrderleft,int preOrderright,int inOrderleft,int inOrderright){
if(inOrderleft>inOrderright||preOrderleft>preOrderright) return null;
//先序數組當前的首個元素對應根節點
int root = preOrder[preOrderleft];
//在中序遍歷中
int middle = inOrderleft;
//在中序中查找root的位置:
//則在中序遍歷左邊的元素爲左子樹中元素,在中序遍歷右邊的的元素爲右子樹
for(int i=inOrderleft;i<=inOrderright;i++){
//在中序遍歷中找到root的位置middle
//因爲元素不重複,所以找到一個元素跳出循環即可
if(inorder[i]==root){
middle=i;
break;
}
}
//在左子樹中元素個數
TreeNode node = new TreeNode(root);
//分支算法
//遞歸
node.left=dfs(preOrder,inorder,preOrderleft+1,middle-inOrderleft+preOrderleft,inOrderleft,middle-1);
node.right=dfs(preOrder,inorder,middle-inOrderleft+preOrderleft+1,preOrderright,middle+1,inOrderright);
return node;
}
}
=======
根據中序遍歷與後序遍歷確定樹
package JDFS;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/4/29 0029 18:15
* 從中序與後序遍歷序列構造二叉樹
*/
public class Problem106 {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length==0||postorder.length==0) return null;
return buildTree(inorder,postorder,0,inorder.length-1,0,postorder.length-1);
}
public TreeNode buildTree(int[] inorder, int[] postorder,int l1,int h1,int l2,int h2){
if(l1>h1||l2>h2) return null;
//後序遍歷根接地那的值(根節點的位置)
int root = postorder[h2];
//中序遍歷中對應根節點的位置
int middle = l1;
//在中序遍歷的位置找到根節點的位置:
//在根節點的左邊的爲左子樹,在根節點的右面爲右子樹(這點在中序遍歷中顯而易見)
//根據左子樹中節點的個數從而確定後序遍歷中左子樹的元素的位置
for(int i=l1;i<=h1;i++){
if(inorder[i]==root){
middle=i;
break;
}
}
TreeNode node = new TreeNode(root);
//左子樹的位置
node.left=buildTree(inorder,postorder,l1,middle-1,l2,l2+middle-l1-1);
node.right=buildTree(inorder,postorder,middle+1,h1,l2+middle-l1,h2-1);
return node;
}
}