二叉樹的前序、中序和後序遍歷是二叉樹學習中的一個重點,也是二叉樹中很基礎的一部分,需要我們熟練的運用;通常已知二叉樹,很多同學都能很輕鬆的得到其前序、中序和後序遍歷的隊列,甚至層序遍歷也不在話下,如果是反過來呢?如果是理解不夠的同學就會覺得頭疼,有點不知道怎麼下手,這裏我將分享一下我的彙總,可能方法不是最優的,但是一定能幫助理解。
在此之前呢,我們需要再回顧一下二叉樹的前序、中序和後序遍歷的特點;
前序遍歷:遍歷後的序列中節點按照
[ 根節點 | 左子樹 | 右子樹 ]
排序,以上圖所示爲例[ 1 | 2 4 5 | 3 6 7 ];中序遍歷:遍歷後的序列中節點按照
[ 左子樹 | 根節點 | 右子樹 ]
排序,以上圖所示爲例[ 4 2 5 | 1 | 6 3 7 ];後序遍歷:遍歷後的序列中節點按照
[ 左子樹 | 右子樹 | 根節點 ]
排序,以上圖所示爲例[ 4 5 2 | 6 7 3 | 1 ];
前序和中序
解題思路
結合前序遍歷和中序遍歷的特點,可以順序完成下列工作:
- 前序遍歷的首個元素就是根節點 root 的值;
- 在中序遍歷中找到根節點 root 的索引,可將中序隊列中左子樹和右子樹劃分出來,即
[ 左子樹 | 根節點 | 右子樹 ]
; - 根據中序隊列中左(右)子樹節點的數量,可將前序隊列中左子樹和右子樹劃分出來,即
[ 根節點 | 左子樹 | 右子樹 ];
根據子樹特點,我們可以通過同樣的方法對左(右)子樹進行劃分,每輪可確認三個節點的關係 。此遞推性質讓我們聯想到用 遞歸方法 處理;但是在開始之前還有一個很重要的事情,就是先對遞歸進行解析;
- 遞推參數: 前序遍歷中根節點的索引 pre_root、中序遍歷左邊界 in_left、中序遍歷右邊界 in_right。
- 終止條件: 當 in_left > in_right ,子樹中序遍歷爲空,說明已經越過葉子節點,此時返回 nullnull 。
- 遞推工作:
- 建立根節點 root: 值爲前序遍歷中索引爲 pre_root 的節點值。
- 搜索根節點root 在中序遍歷的索引 i: 爲了提升搜索效率,本題解使用哈希表 dic 預存儲中序遍歷的值與索引的映射關係,每次搜索的時間複雜度爲 O(1)。
- 構建根節點root的左子樹和右子樹: 通過調用 recur() 方法開啓下一層遞歸。
- 左子樹: 根節點索引爲 pre_root + 1 ,中序遍歷的左右邊界分別爲 in_left 和 i - 1。
- 右子樹: 根節點索引爲 i - in_left + pre_root + 1(即:根節點索引 + 左子樹長度 + 1),中序遍歷的左右邊界分別爲 i + 1 和 in_right。
- 返回值: 返回 root,含義是當前遞歸層級建立的根節點 root 爲上一遞歸層級的根節點的左或右子節點
class Solution {
//定義一個map用來存儲中序序列,方便直接利用根節點的值找其在中序序列中的索引
HashMap<Integer, Integer> in_map = new HashMap<>();
//定義一個全局變量preArr,用於指向前序序列,方便在recur方法中使用
int[] preArr;
public TreeNode buildTree(int[] preorder, int[] inorder) {
preArr = preorder;
for(int i = 0; i < inorder.length; i++)
in_map.put(inorder[i], i);
return recur(0, 0, inorder.length-1);
}
/**
* recur方法開啓下一層遞歸
* pre_root 前序遍歷中根節點的索引
* in_left 中序遍歷左邊界
* in_right 中序遍歷右邊界
*/
public TreeNode recur(int pre_root, int in_left, int in_right){
//遞歸先判斷結束條件;當 in_left > in_right ,子樹中序遍歷爲空,說明已經越過葉子節點,此時返回 null 。
if(in_left > in_right)
return null;
//建立當前子樹的根節點
TreeNode root = new TreeNode(preArr[pre_root]);
//搜索根節點在中序遍歷中的索引,從而可對根節點、左子樹、右子樹完成劃分
int i = in_map.get(preArr[pre_root]);
//開啓左子樹的下層遞歸
root.left = recur(pre_root+1, in_left, i-1);
//開啓右子樹的下層遞歸
root.right = recur(pre_root+i-in_left+1, i+1, in_right);
return root;
}
}
中序和後序
後序和前序是比較類似的序列,前序序列的第一個元素爲樹的根節點,而後序序列的根節點則是在最後一個元素;因此,如果掌握了前序和中序,那麼中序和後序的原理也基本相同,唯一的區別就是位置關係的計算,結合下面這張圖去看代碼可以幫助大家去理解;
class Solution {
HashMap<Integer, Integer> in_map = new HashMap<>();
int[] posArr;
public TreeNode buildTree(int[] inorder, int[] postorder) {
posArr = postorder;
for(int i = 0; i < inorder.length; i++)
in_map.put(inorder[i], i);
return recur(postorder.length-1, 0, 0, inorder.length-1);
}
public TreeNode recur(int pre_root, int pre_left, int in_left, int in_right){
if(in_left > in_right || pre_root < pre_left)
return null;
TreeNode root = new TreeNode(posArr[pre_root]);
int i = in_map.get(posArr[pre_root]);
root.left = recur(pre_left+i-in_left-1, pre_left, in_left, i-1);
root.right = recur(pre_root-1, pre_left+i-in_left, i+1, in_right);
return root;
}
}
前序和後序
前序和後序這兩個序列比較相似,都能直接找到根節點,而沒有任何一個序列能夠將左右子樹進行分離,所以實現起來更爲麻煩;具體思路爲,我們在前序序列中可以找到根節點和左子樹的根節點(根節點後面的節點),我們可以在後序序列中找到這個左子樹根節點的索引,從而得到左子樹的長度,這樣就可以分別在前序和後序序列中將根節點、左子樹和右子樹劃分出來,我們還結合圖形進行理解;
class Solution {
HashMap<Integer, Integer> preMap = new HashMap<>();
HashMap<Integer, Integer> postMap = new HashMap<>();
int[] preArr;
public TreeNode constructFromPrePost(int[] pre, int[] post) {
preArr = pre;
for(int i = 0; i < pre.length; i++)
preMap.put(pre[i], i);
for(int i = 0; i < post.length; i++)
postMap.put(post[i], i);
return recur(0, pre.length-1, 0, post.length-1);
}
public TreeNode recur(int pre_left, int pre_right, int post_left, int post_right){
if(pre_left > pre_right || post_left > post_right)
return null;
//這裏需要注意,新增加了兩種判斷情況,因爲如果相等的時候證明這已經是葉子節點無需再向下遞歸
//因爲left和right都是計算出來的,所以再遞歸也不會滿足left>right,而會出現數組下標越界的情況
if(pre_left == pre_right)
return new TreeNode(preArr[pre_left]);
if(post_left == post_right)
return new TreeNode(preArr[post_left]);
TreeNode root = new TreeNode(preArr[pre_left]);
int left_len = postMap.get(preArr[pre_left+1]) - post_left;
root.left = recur(pre_left+1, pre_left+1+left_len, post_left, postMap.get(preArr[pre_left+1]));
root.right = recur(pre_left+left_len+2, pre_right, postMap.get(preArr[pre_left+1])+1, post_right-1);
return root;
}
}