二叉樹(BST)中序遍歷的三種方法
介紹二叉樹中序遍歷的三種方法,分別是:遞迴(隱式的維護了一個棧)、基於棧的迭代、Morris中序遍歷
首先:什麼是二叉樹中序遍歷:
按照訪問左子樹——根節點——右子樹的方式遍歷這棵樹,而在訪問左子樹或者右子樹的時候我們按照同樣的方式遍歷,直到遍歷完整棵樹。
1.使用遞迴實現中序遍歷:
二叉樹的中序遍歷的整個遍歷過程本身就具有遞迴的性質,可以直接用遞迴函式來模擬這一過程。
定義 inorder(root) 表示當前遍歷到root節點的答案,那麼按照定義,我們只要遞迴呼叫 inorder(root.left) 來遍歷root節點的左子樹,然後將 left root節點的值加入答案,再遞迴呼叫inorder(root.right) 來遍歷 root 節點的右子樹即可,遞迴終止的條件為碰到空節點。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
inorder(root,list);
return list;
}
private void inorder(TreeNode root,List<Integer> list){
//this is the end statement
if(root==null){
return;
}
inorder(root.left);
list.add(root.val);
inorder(root.right);
}
複雜度分析
時間複雜度:O(n), n 為二叉樹節點的個數。因為遞迴的遍歷中每個節點只會被訪問一次。
空間複雜度:O(n)。在最壞情況下,二叉樹退化為連結串列,此時棧的深度為二叉樹的長度n。
2.使用基於棧的迭代實現中序遍歷
其實在第一種方法裡,遞迴的模擬中序遍歷會隱式的維護一個棧,那如果直接把這個棧顯示的模擬出來,即可使用迭代的方法
這裡使用雙端佇列Deque,目的是想在列表頭和列表尾都實現增添和刪除操作
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Deque<TreeNode> deque = new LinkedList<>();
while (root!=null||!deque.isEmpty()){//這兩者都不成立的時候,直接返回,因為此時root跟為空
//build a stack contains of all left element,for each element,next will deal with
//their right element
while (root!=null){
deque.add(root);
root=root.left;
}
root = deque.poll();//pull the leftest element
list.add(root.val);//add it's value into ans list
root=root.right;//search for it's right node
}
return list;
}
複雜度分析
時間複雜度:O(n),同上,每個節點只會遍歷一次
空間複雜度:O(n)。同上,在最壞情況下,二叉樹退化為連結串列,此時棧的深度為二叉樹的長度n。
3。使用Morris遍歷演算法實現中序遍歷
Morris 遍歷演算法是另一種遍歷二叉樹的方法,它能將非遞迴的中序遍歷空間複雜度降為 O(1)O(1)。
Morris 遍歷演算法整體步驟如下(假設當前遍歷到的節點為 xx):
1.如果 x 無左孩子,先將 x 的值加入答案陣列,再訪問 x 的右孩子,即x=x.right。
2.如果 x 有左孩子,則找到 x 左子樹上最右的節點(即左子樹中序遍歷的最後一個節點,x 在中序遍歷中的前驅節點),我們記為 predecessor。根據 predecessor 的右孩子是否為空,進行如下操作。
如果 predecessor 的右孩子為空,則將其右孩子指向 x,然後訪問 x 的左孩子,x=x.left。
如果 predecessor 的右孩子不為空,則此時其右孩子指向 x,說明我們已經遍歷完 x 的左子樹,我們將 predecessor 的右孩子置空,將 x 的值加入答案陣列,然後訪問 x 的右孩子,x=x.right。
重複上述操作,直至訪問完整棵樹。
本質上:假設當前遍歷到的節點為 x,將 x 的左子樹中最右邊的節點的右孩子指向 x,這樣在左子樹遍歷完成後我們通過這個指向走回了 x,且能通過這個指向知曉我們已經遍歷完成了左子樹,而不用再通過棧來維護,省去了棧的空間複雜度。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
TreeNode predecessor = null;
while(root!=null){
// predecessor 節點就是當前 root 節點向左走一步,然後一直向右走至無法走為止
if(root.left!=null){
predecessor=root.left;
while(predecessor.right!=null&&predecessor.right!=root){
predecessor=predecessor.right;
}
//如果Predecessor右邊節點為空,那就把它指向根
if(predecessor.right==null){
predecessor.right = root;
root = root.left;
}
//如果不為空,那就說明這個左樹其實已經指向root了,已經遍歷完 xx 的左子樹
else {
list.add(root.val);
predecessor.right =null;
root=root.right;
}
}
//不然左為空,就直接把當前加入答案,然後檢視右樹
else {
list.add(root.val);
root=root.right;
}
}
return list;
}
複雜度分析
時間複雜度:O(n),其中 n 為二叉搜尋樹的節點個數。Morris 遍歷中每個節點會被訪問兩次,因此總時間複雜度為 O(n)。
空間複雜度:O(1)。因為並不需要維護一個深度為n的棧(在極壞情況下)
相關文章
- 二叉樹的前序、中序、後序三種遍歷二叉樹
- 二叉樹的四種遍歷方法:先序,中序,後序,層序二叉樹
- 144.二叉樹的前序遍歷145.二叉樹的後序遍歷 94.二叉樹的中序遍歷二叉樹
- 二叉樹的前序,中序,後序遍歷方法總結二叉樹
- 二叉樹的先中後序遍歷二叉樹
- 二叉樹的先,中,後序遍歷二叉樹
- 二叉樹的前中後序遍歷二叉樹
- 根據二叉樹的前序遍歷和中序遍歷輸出二叉樹;二叉樹
- Python實現二叉樹的三種深度遍歷方法!Python二叉樹
- 二叉樹的層序遍歷二叉樹
- 二叉樹--後序遍歷二叉樹
- 【Algorithm&DataStructure】二叉查詢樹(BST)的遍歷GoASTStruct
- Leetcode——94.二叉樹的中序遍歷LeetCode二叉樹
- 94. 二叉樹的中序遍歷(迭代)二叉樹
- LeeCode-94. 二叉樹的中序遍歷二叉樹
- 二叉樹中序和後序遍歷表示式二叉樹
- 二叉樹遍歷順序與方法小結二叉樹
- 根據前序遍歷序列、中序遍歷序列,重建二叉樹二叉樹
- 中序線索二叉樹的建立與遍歷二叉樹
- 【模板題】- 94. 二叉樹的中序遍歷二叉樹
- 二叉樹遍歷方法二叉樹
- 二叉樹四種遍歷二叉樹
- 從中序與後序遍歷序列構造二叉樹二叉樹
- 中序線索二叉樹的構造和遍歷二叉樹
- python-二叉樹:前、中、後、層序遍歷Python二叉樹
- 面試中很值得聊的二叉樹遍歷方法——Morris遍歷面試二叉樹
- 二叉樹:構造二叉樹(通過前序和中序遍歷)、映象翻轉、層次遍歷二叉樹
- 144. 二叉樹的遍歷「前序、中序、後序」 Golang實現二叉樹Golang
- 一文弄懂二叉樹的三種遍歷方式二叉樹
- 二叉搜尋樹的後序遍歷序列
- LeetCode102.二叉樹的層序遍歷LeetCode二叉樹
- 程式碼隨想錄演算法訓練營day14 | leetcode 144. 二叉樹的前序遍歷、145. 二叉樹的後序遍歷、94. 二叉樹的中序遍歷演算法LeetCode二叉樹
- 非遞迴先序遍歷二叉樹遞迴二叉樹
- 388,先序遍歷構造二叉樹二叉樹
- 演算法 -- 實現二叉樹先序,中序和後序遍歷演算法二叉樹
- 二叉樹遍歷方法總結二叉樹
- 【模板題】- 145. 二叉樹的後序遍歷二叉樹
- LeetCode-107-二叉樹的層序遍歷 IILeetCode二叉樹