樹的中序遍歷(遞歸,迭代,莫里斯)

中序遍歷
所有涉及到中序遍歷的題都可以使用該模板解決
中序遍歷(inorderTraversal)有遞歸,迭代,莫里斯三種解法

遞歸版

public List<Integer> inorderTraversal(TreeNode root) {
	//具體的細節可以和這裏不一樣,但思路一致即可,就是左子樹遞歸->root->右子樹遞歸
    List<Integer> ans = new ArrayList<>();
    getAns(root, ans);
    return ans;
}

private void getAns(TreeNode node, List<Integer> ans) {
    if (node == null) {
        return;
    }
    getAns(node.left, ans); 
    ans.add(node.val);
    getAns(node.right, ans);
}

時間複雜度:O(n),遍歷每個節點。

空間複雜度:O(h),壓棧消耗,h 是二叉樹的高度。

迭代版

Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()){
	while(cur != null){
		stack.push(cur);
		cur = cur.left;
	}
	cur = stack.pop();
	/** do something **/
	cur = cur.right;
}

時間複雜度:O(n)。

空間複雜度:O(h),棧消耗,h 是二叉樹的高度。

Morris遍歷

解法一和解法二本質上是一致的,都需要 O(h)的空間來保存上一層的信息。而我們注意到中序遍歷,就是遍歷完左子樹,然後遍歷根節點。如果我們把當前根節點存起來,然後遍歷左子樹,左子樹遍歷完以後回到當前根節點cur就可以了,怎麼做到呢?
我們注意到左子樹cur.left最右邊的節點last的右孩子是null,我們可以將last.right = cur即保留當前根節點即可,這樣我們遍歷完左子樹,就可以回到根節點了。
如果當前根節點的左子樹爲null,那我們就直接遍歷根節點,再考慮右子樹cur = cur.right;

所以總體思想就是:記當前遍歷的節點爲 cur:

  1. cur.left 爲null,保存cur的值,更新cur爲cur.right;
  2. cur.left不爲null,記prev = cur.left,找到左子樹的最右邊節點記爲last;
  3. 如果last.right爲null,將last.right = cur;更新cur = cur.left;
  4. 如果last.right不爲null,說明之前已經訪問過,第二次來到這裏,表明當前子樹遍歷完成,將last.right = null;保存cur的值,更新cur爲cur.right。
    結合圖示:
    在這裏插入圖片描述
    如上圖,cur 指向根節點。 當前屬於 3 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子爲 null,那麼我們把最右邊的節點的右孩子指向 cur。
    在這裏插入圖片描述
    接着,更新 cur = cur.left。
    在這裏插入圖片描述
    如上圖,當前屬於 3 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子爲 null,那麼我們把最右邊的節點的右孩子指向 cur。
    在這裏插入圖片描述
    更新 cur = cur.left。
    在這裏插入圖片描述
    如上圖,當前屬於情況 1,cur.left 爲 null,保存 cur 的值,更新 cur = cur.right。
    在這裏插入圖片描述
    如上圖,當前屬於 4 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子已經指向 cur,保存 cur 的值,更新 cur = cur.right。
    在這裏插入圖片描述
    如上圖,當前屬於情況 1,cur.left 爲 null,保存 cur 的值,更新 cur = cur.right。
    在這裏插入圖片描述
    如上圖,當前屬於4的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子已經指向 cur,保存 cur 的值,更新 cur = cur.right。
    在這裏插入圖片描述
    當前屬於情況 1,cur.left 爲 null,保存 cur 的值,更新 cur = cur.right。
    在這裏插入圖片描述
    cur 指向 null,結束遍歷。

根據這個關係,寫代碼:

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    TreeNode cur = root;
	while(cur != null){
		if(cur.left == null){
			ans.add(cur.val);  //do something
			cur = cur.right;
		}else{
			TreeNode prev = cur.left;
			while(prev.right != null && prev.right != cur)
				prev = prev.right;
			//情況3	
			if(prev.right == null){
				prev.right = cur;
				cur = cur.left;
			}	
			//情況4,不用擔心前一個if會影響後一個,因爲cur已經改變了
			if(prev.right == cur){
				prev.right = null;
				ans.add(cur.val);  //do something
				cur = cur.right;
			}
			
		}
	}
	return ans;   //依據實際情況返回

時間複雜度:O(n)。每個節點遍歷常數次。

空間複雜度:O(1)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章