1. 程式人生 > >Leetcode99:恢復二叉搜尋樹

Leetcode99:恢復二叉搜尋樹

題目描述:

二叉搜尋樹中的兩個節點被錯誤地交換。請在不改變其結構的情況下,恢復這棵樹。
示例 1:
輸入: [1,3,null,null,2]

 1
/
3
  \
   2

輸出: [3,1,null,null,2]

  3
/
1
  \
    2

解法一:

來自:https://leetcode.com/problems/recover-binary-search-tree/discuss/32535/No-Fancy-Algorithm-just-Simple-and-Powerful-In-Order-Traversal
利用樹的中序遍歷;
解釋:
中序遍歷二叉樹,並維持三個變數:pre,first和second,分別表示:前一個訪問的節點,第一個需要交換的節點和第二個需要交換的節點;
中序遍歷時,如果是排序二叉樹,應該是遞增序列;但該數由於存在兩個節點發生了交換,所以非遞增序列;我們要找的就是破壞了遞增規則的第一個節點和第二個節點
如[6,3,4,5,2],這是中序遍歷的結果;從後向前遍歷;
判斷破壞規則的標準是:
找first:找序列中第一次出現的當前節點小於前一節點的位置;則前一節點即為first;
找second:因為此時已經找到first了,之後破壞規則的肯定是值小於前一節點的節點;即找當前節點值小於前一節點值的,即為second
所以,第一個破壞規則的數字是6;第二個破壞規則的是2
所以整個流程是:中序遍歷,記錄上一個訪問的節點;將上一訪問節點與當前節點比較(這兩個節點在中序遍歷序列中是相鄰的關係),如果first還未找到,且pre.val>root.val,則first應該為pre節點;
若first已找到,且pre.val>root.val,則second應該為root節點。
程式碼:

public class RecoverTree {

	TreeNode preElement = null;
	TreeNode firstElement = null;
	TreeNode secondElement = null;

	public void recoverTree(TreeNode root) {
		if (root == null)
			return;
		preElement = new TreeNode(Integer.MIN_VALUE);
		traverse(root);
		int temp = firstElement.val;
		firstElement.
val = secondElement.val; secondElement.val = temp; } private void traverse(TreeNode root) { if (root == null) return; traverse(root.left); // 如果first節點未找到,且前一節點值大於當前節點值,說明找到第一個破壞規則的節點位置,即preElement if (firstElement == null && preElement.val > root.val) { firstElement = preElement;
} // first節點已找到,且前一節點值大於當前節點值,找到第二個破壞規則的節點位置,即root if (firstElement != null && preElement.val > root.val) { secondElement = root; } // 更新上一訪問節點 preElement = root; traverse(root.right); } }

解法二:

來自:https://leetcode.com/problems/recover-binary-search-tree/discuss/32559/Detail-Explain-about-How-Morris-Traversal-Finds-two-Incorrect-Pointer

這種方法是基於Morris Traversal(遍歷)的方法,所以先介紹一下Morris Traversal。
來自:http://www.cnblogs.com/AnnieKim/archive/2013/06/15/morristraversal.html

Morris Traversal:
以中序遍歷為例,實現樹的中序遍歷,一般有兩種方法:
1)遞迴實現,時間複雜度O(n),空間複雜度O(n)
2)棧+迭代實現,時間複雜度O(N),空間複雜度O(n)
先希望有一種方法,可以降低空間複雜度為O(1),這就是Morris Traversal方法。
該方法基於樹的線索化,實現一次遍歷,在O(1)複雜度的條件下,完成樹的遍歷。

中序遍歷
步驟:
1. 如果當前節點的左孩子為空,則輸出當前節點並將其右孩子作為當前節點。
2. 如果當前節點的左孩子不為空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。
a) 如果前驅節點的右孩子為空,將它的右孩子設定為當前節點。當前節點更新為當前節點的左孩子。
b) 如果前驅節點的右孩子為當前節點,將它的右孩子重新設為空(恢復樹的形狀)。輸出當前節點。當前節點更新為當前節點的右孩子。
3. 重複以上1、2直到當前節點為空。

解釋到我能明白的地步:
在對樹進行遍歷時,之所以用棧,是因為節點沒有記錄其父節點的資訊,如果不用棧記錄,父節點資訊就會丟失。這裡,我們使用葉節點的空指標域,進行節點間關係的記錄。
當前節點的前驅節點(以中序遍歷為例,其前驅節點就是左子樹中序遍歷的最後一個節點),的右孩子,指向當前節點(記錄了與父節點的關係);然後遞迴遍歷當前節點的左子樹。

程式碼:

public class MorrisTraversal {

	public void morrisTraversal(TreeNode root) {
		// MorrisTraversal 中序遍歷
		List<Integer> list = new ArrayList<Integer>();
		TreeNode curr = root;
		while (curr != null) {
			if (curr.left == null) {
				list.add(curr.val);
				curr = curr.right;
				continue;
			}
			TreeNode preNode = findPreNode(curr);
			if (preNode.right == curr) {
				preNode.right = null;
				list.add(curr.val);
				curr = curr.right;
			} else {
				preNode.right = curr;
				curr = curr.left;
			}
		}
		System.out.println(list.toString());
	}

	private TreeNode findPreNode(TreeNode root) {
		// TODO Auto-generated method stub
		if (root == null || root.left == null)
			return null;
		TreeNode curr = root.left;
		while (curr.right != null && curr.right != root) {
			curr = curr.right;
		}
		return curr;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MorrisTraversal morrisTraversal = new MorrisTraversal();
		TreeNode root = new TreeNode(5);
		TreeNode node1 = new TreeNode(3);
		TreeNode node2 = new TreeNode(6);
		TreeNode node3 = new TreeNode(2);
		TreeNode node4 = new TreeNode(4);
		root.left = node1;
		root.right = node2;
		node1.left = node3;
		node1.right = node4;
		morrisTraversal.morrisTraversal(root);

	}

}

至於前序和中序,這裡先不說了= 3=

用Morris Traversal實現恢復二叉搜尋樹,只需要在輸出節點的位置(輸出節點的位置,表示該節點在中序遍歷中的位置已確定),進行中序遍歷相鄰節點間的判斷即可,程式碼如下。
程式碼:

public class MorrisTraversal {

	TreeNode previousNode = null;
	TreeNode firstNode = null;
	TreeNode secondNode = null;

	public void recoverTree(TreeNode root) {
		// MorrisTraversal實現recoverTree
		// List<Integer> list = new ArrayList<Integer>();
		previousNode = new TreeNode(Integer.MIN_VALUE);
		TreeNode curr = root;
		while (curr != null) {
			if (curr.left == null) {
				// list.add(curr.val);
				if (firstNode == null && previousNode.val > curr.val) {
					firstNode = previousNode;
				}
				if (firstNode != null && previousNode.val > curr.val) {
					secondNode = curr;
				}
				previousNode = curr;
				curr = curr.right;
			} else {
				TreeNode preNode = findPreNode(curr);
				if (preNode.right == curr) {
					preNode.right = null;
					if (firstNode == null && previousNode.val > curr.val) {
						firstNode = previousNode;
					}
					if (firstNode != null && previousNode.val > curr.val) {
						secondNode = curr;
					}
					previousNode = curr;
					curr = curr.right;
				} else {
					preNode.right = curr;
					curr = curr.left;
				}
			}
		}
		// 交換這兩個節點的值
		int temp = firstNode.val;
		firstNode.val = secondNode.val;
		secondNode.val = temp;
	}
	
	private TreeNode findPreNode(TreeNode root) {
		// TODO Auto-generated method stub
		if (root == null || root.left == null)
			return null;
		TreeNode curr = root.left;
		while (curr.right != null && curr.right != root) {
			curr = curr.right;
		}
		return curr;
	}
}