二叉搜索树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
示例 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 list = new ArrayList();
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;
}
}