根據前序遍歷序列、中序遍歷序列,重建二叉樹
題目
來自劍指Offer的第7題。
知識儲備
要完成這道題,對於二叉樹的前序遍歷和後序遍歷一定要理解到位,下方連結是我之前寫的文章,請在做該題目之前閱讀完以下文章。
二叉樹前序、中序、後序遍歷
總體思路
二叉樹的前序遍歷順序是:根節點、左子樹、右子樹,每個子樹的遍歷順序同樣滿足前序遍歷順序。
二叉樹的中序遍歷順序是:左子樹、根節點、右子樹,每個子樹的遍歷順序同樣滿足中序遍歷順序。
前序遍歷的第一個節點是根節點,只要找到根節點在中序遍歷中的位置,在根節點之前被訪問的節點都位於左子樹,在根節點之後被訪問的節點都位於右子樹,由此可知左子樹和右子樹分別有多少個節點。
由於樹中的節點數量與遍歷方式無關,通過中序遍歷得知左子樹和右子樹的節點數量之後,可以根據節點數量得到前序遍歷中的左子樹和右子樹的分界,因此可以進一步得到左子樹和右子樹各自的前序遍歷和中序遍歷,可以通過遞迴的方式,重建左子樹和右子樹,然後重建整個二叉樹。
幾點思考
思考1:
舉個例子:
如果二叉樹只有3個節點,前序:1,2,3,中序:2,1,3
前序第一個數為根節點,故根節點為1,在中序裡找到根節點1,其左為左子樹,右為右子樹
故這棵樹的形狀:根節點1,左子節點2,右子節點3
思考2:
所謂“重建二叉樹”,就是建立出各個節點,並讓父子節點相連。最後返回整棵樹的根節點。
思考3:
序列特點:
前序序列:根節點-左子樹的節點們-右子樹的節點們(子樹的節點們又可以這樣分)
中序序列:左子樹的節點們-根節點-右子樹的節點們(子樹的節點們又可以這樣分)
思考4:
如何從中序序列陣列裡找出根節點的位置?
選擇使用Map來儲存中序序列陣列的index和值,來快速進行陣列裡值的查詢
思考:為什麼不用Arrays類的binarySearch方法進行二分查詢?如果樹太大了,多次進行二分查詢效率低。
思考5:
遞迴時,處理的陣列是不一樣的(樹不一樣),怎麼實現?
一開始處理的陣列是給出的兩個陣列,後來變成陣列的子陣列,子陣列的子陣列,即樹的子樹,子樹的子樹。
用start、end來指明處理的是陣列中的哪一個部分,隨著遞迴而改變。前序序列陣列有start、end,後序序列陣列也有start、end。
思考6:
遞迴函式要做什麼?
1.更新前序和中序序列的start、end
2.根節點連線左子樹,連線右子樹
3.返回根節點
程式碼
先根據2個前序和中序遍歷的陣列,重建了二叉樹。再呼叫前序遍歷方法、中序遍歷方法,來遍歷此二叉樹。觀察遍歷的順序是否和給出的兩個陣列的順序相同。
import java.util.HashMap;
import java.util.Map;
public class JZO_07 {
/*07. 重建二叉樹*/
/*輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。
假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。*/
public static void main(String[] args) {
int[] preorder = {3,9,20,15,7};
int[] inorder = {9,3,15,20,7};
//構建二叉樹
TreeNode root = buildTree(preorder,inorder);
//先序遍歷
recursionPreorderTraversal(root);
System.out.println();
//中序遍歷
recursionMiddleorderTraversal(root);
}
public static TreeNode buildTree(int[] preorder, int[] inorder) {
//判斷2個序列是否合法
if(preorder == null || preorder.length == 0 || inorder == null || inorder.length == 0){
return null;
}
//建立Map放中序序列陣列,便於從中序序列陣列裡找出根節點的位置
Map<Integer,Integer> inorderIndexMap = new HashMap<Integer, Integer>();
for(int i=0;i<inorder.length;i++){
//用陣列的值來找陣列的索引,所以應該將陣列的值當做Map的鍵
//題目說了節點值不重複,所以作為鍵不會重複
inorderIndexMap.put(inorder[i],i);
}
//遞迴構造樹
TreeNode root = connectRootAndSubtree(preorder,0,preorder.length-1,
inorder,0,inorder.length-1,inorderIndexMap);
return root;
}
public static TreeNode connectRootAndSubtree(int[] preorder,int pStart,int pEnd,
int[] inorder,int iStart,int iEnd,Map<Integer,Integer> inorderIndexMap){
//結束遞迴的條件:start、end指標錯位了
if((pStart > pEnd)||(iStart > iEnd)){
return null;
}
//得到根節點的值
int rootVal = preorder[pStart];
//構造根節點
TreeNode root = new TreeNode(rootVal);
//得到根節點在中序序列的位置
int rootIndex = inorderIndexMap.get(rootVal);
//得到左子樹對應序列的長度(左子樹節點的個數)
int leftSubtreeLength = rootIndex - iStart;
//得到右子樹對應序列的長度(右子樹節點的個數)
int rightSubtreeLength = iEnd - rootIndex;
//前序序列中,左子樹、右子樹對應的start、end
//前序序列的左子樹的start
int pStartLeftSubtree = pStart+1;
//前序序列的左子樹的end
int pEndLeftSubtree = pStartLeftSubtree+leftSubtreeLength-1;
//前序序列的右子樹的start
int pStartRightSubtree = pEndLeftSubtree+1;
//前序序列的右子樹的end
int pEndRightSubtree = pEnd;
//中序序列的左子樹的start
int iStartLeftSubtree = iStart;
//中序序列的左子樹的end
int iEndLeftSubtree = rootIndex-1;
//中序序列的右子樹的start
int iStartRightSubtree = rootIndex+1;
//中序序列的右子樹的end
int iEndRightSubtree = iEnd;
//得到左子樹的根節點
TreeNode leftSubtreeRoot = connectRootAndSubtree(preorder,pStartLeftSubtree,
pEndLeftSubtree,inorder,iStartLeftSubtree,iEndLeftSubtree,inorderIndexMap);
//得到右子樹的根節點
TreeNode rightSubtreeRoot = connectRootAndSubtree(preorder,pStartRightSubtree,
pEndRightSubtree,inorder,iStartRightSubtree,iEndRightSubtree,inorderIndexMap);
//連線根節點與左右子樹
root.left = leftSubtreeRoot;
root.right = rightSubtreeRoot;
return root;
}
// 遞迴先序遍歷
public static void recursionPreorderTraversal(TreeNode root) {
if (root != null) {
System.out.print(root.val + " ");
recursionPreorderTraversal(root.left);
recursionPreorderTraversal(root.right);
}
}
// 遞迴中序遍歷
public static void recursionMiddleorderTraversal(TreeNode root) {
if (root != null) {
recursionMiddleorderTraversal(root.left);
System.out.print(root.val + " ");
recursionMiddleorderTraversal(root.right);
}
}
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
//執行用時:3 ms, 在所有 Java 提交中擊敗了82.60%的使用者
//記憶體消耗:38.9 MB, 在所有 Java 提交中擊敗了82.98%的使用者
執行結果
畫圖輔助理解:在陣列裡劃分左子樹部分、右子樹部分
在計算前序序列中,左子樹、右子樹對應的start、end;
以及,在計算中序序列中,左子樹、右子樹對應的start、end的時候,
思路容易混亂,我們畫圖來整理一下思路。
相關文章
- 根據二叉樹的前序遍歷和中序遍歷輸出二叉樹;二叉樹
- 二叉樹建立,前序遍歷,中序遍歷,後序遍歷 思路二叉樹
- 二叉樹的建立、前序遍歷、中序遍歷、後序遍歷二叉樹
- 889. 根據前序和後序遍歷構造二叉樹二叉樹
- 【根據前序和中序遍歷構造二叉樹】棧+迭代 || 遞迴二叉樹遞迴
- LintCode 前序遍歷和中序遍歷樹構造二叉樹二叉樹
- 資料結構與演算法——二叉樹的前序遍歷,中序遍歷,後序遍歷資料結構演算法二叉樹
- 從中序與後序遍歷序列構造二叉樹二叉樹
- 演算法根據樹的前序遍歷構建二叉樹演算法二叉樹
- 144.二叉樹的前序遍歷145.二叉樹的後序遍歷 94.二叉樹的中序遍歷二叉樹
- 二叉樹的前序、中序、後序三種遍歷二叉樹
- 關於二叉樹的前序遍歷、中序遍歷、刪除元素、插入元素二叉樹
- LeetCode 105. 從前序與中序遍歷序列構造二叉樹LeetCode二叉樹
- LeetCode-105-從前序與中序遍歷序列構造二叉樹LeetCode二叉樹
- Leetcode 889. 根據前序和後序遍歷構造二叉樹LeetCode二叉樹
- 二叉樹的前序,中序,後序遍歷方法總結二叉樹
- 劍指offer:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。二叉樹
- 二叉樹:構造二叉樹(通過前序和中序遍歷)、映象翻轉、層次遍歷二叉樹
- LeetCode 105. 從前序與中序遍歷序列構造二叉樹 | PytLeetCode二叉樹
- Construct Binary Tree from Preorder and Inorder Traversal(前序遍歷和中序遍歷樹構造二叉樹)...Struct二叉樹
- C4top-玩轉二叉樹(根據前序中序映象反轉後層次遍歷)二叉樹
- 刷題筆記:樹的前序、中序、後序遍歷筆記
- 二叉樹--後序遍歷二叉樹
- 層序遍歷二叉樹二叉樹
- 144. 二叉樹的遍歷「前序、中序、後序」 Golang實現二叉樹Golang
- 144. 二叉樹的前序遍歷二叉樹
- 二叉樹迭代器(中序遞迴、前序和後序遍歷)演算法二叉樹遞迴演算法
- 根據二叉樹的先序序列和中序序列還原二叉樹並列印後序序列二叉樹
- 二叉樹的層序遍歷二叉樹
- 二叉樹的先,中,後序遍歷二叉樹
- 二叉樹的先中後序遍歷二叉樹
- 二叉樹的前中後序遍歷二叉樹
- 【LeetCode-二叉樹】二叉樹前序遍歷LeetCode二叉樹
- 個人練習之二叉樹的前序遍歷二叉樹
- 二叉樹中序和後序遍歷表示式二叉樹
- 二叉樹前序、中序、後序遍歷相互求法(code留著看,概念先看了)二叉樹
- 106. 從中序與後序遍歷序列構造二叉樹——Java實現二叉樹Java
- LeetCode-106-從中序與後序遍歷序列構造二叉樹LeetCode二叉樹