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);
}
}
解法二:
這種方法是基於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;
}
}