题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
Again:
从BG某公司面试栽到这道题,重新做一遍发现还是不会,我一直考虑如何在遍历的同时就把指针给转换完毕。
第一种方法是这样:我使用一个数组保存中序遍历的结果,然后在数组中对它进行指针的转换即可。看到这个做法,惊掉了我的大牙!!!!我怎么就想不到先把它放进一个数组,之后再对其进行转换呢!!!哎,多好的一份实习,没了。。。。
对树的算法的套路:要么遍历完处理;要么遍历的同时处理。
中序遍历的时候,当当前节点为null的时候,弹出栈中的元素。
public class Solution {
ArrayList<TreeNode> list = new ArrayList();
public void InOrder(TreeNode root){
if(root != null){
InOrder(root.left);
list.add(root);
InOrder(root.right);
}
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
InOrder(pRootOfTree);
if(list.size() == 1)
return list.get(0);
list.get(0).left = null;
list.get(0).right = list.get(1);
list.get(list.size()-1).left = list.get(list.size()-2);
list.get(list.size()-1).right = null;
for(int i=1;i<list.size()-1;i++){
list.get(i).left = list.get(i-1);
list.get(i).right = list.get(i+1);
}
return list.get(0);
}
}
第二种是在遍历的同时进行指针转换,有两点很重要:
(1)保存上一个访问的节点
(2)最后返回的节点不再是根节点,而是最左的那个节点
public class Solution {
ArrayList<TreeNode> list = new ArrayList();
public TreeNode InOrder(TreeNode root){
if(root == null)
return null;
TreeNode p = root;
TreeNode lastNode = null;
Stack<TreeNode> stack = new Stack();
while(!stack.isEmpty() || p!= null){
while(p != null){
stack.push(p);
p = p.left;
}
p = stack.pop();
p.left = lastNode;
if(lastNode != null)
lastNode.right = p;
lastNode = p;
p = p.right;
}
lastNode.right = null;
while(root.left != null)
root = root.left;
return root;
}
public TreeNode Convert(TreeNode pRootOfTree) {
return InOrder(pRootOfTree);
}
}
而且在递归中改变指针也没想象中的那么难,根本就不用返回值。如果真的想返回值的话,在递归的形式中,一旦某孩子的节点为null时,那么返回的上一个节点直接就变为null了,会出现问题,不能正确的返回真正的值;而且一个左边,一个右边,返回的是哪一个呢?
解决方式就是,当其左右孩子不为null的时候,返回上一个节点。
import java.util.Stack;
public class Solution {
TreeNode lastNode = null;
public void InOrder(TreeNode root){
if(root != null){
InOrder(root.left);
root.left = lastNode;
if(lastNode != null)
lastNode.right = root;
lastNode = root;
InOrder(root.right);
}
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
InOrder(pRootOfTree);
lastNode.right = null;
while(pRootOfTree.left != null){
pRootOfTree = pRootOfTree.left;
}
return pRootOfTree;
}
}
带着返回值的,是不是递归形式下需要返回值的都这么写呀?
使用lastNode指向已经转换好的链表的最后一个节点(也是值最大的节点)
为什么要判断root的左右还是是不是为null呢?如果左孩子为null,那么lastNode指向的就不再是转换好的链表的最后一个节点,而是null;尤其是右孩子,如果右孩子不为null,那么对右孩子递归,处理右孩子的left和上一个节点的right,然后lastNode就指向了这个右孩子(这一块建议画图)==》即右孩子不为null,lastNode指向的就是右孩子;如果右孩子为null了,lastNode本应该指向已经转换好的最后一个节点,但是,此时会直接返回null,lastNode就指向了null;那么这里之后就都错误了!
public class Solution {
TreeNode lastNode = null;
public TreeNode InOrder(TreeNode root){
if(root != null){
if(root.left != null)
lastNode = InOrder(root.left);
root.left = lastNode;
if(lastNode != null)
lastNode.right = root;
lastNode = root;
if(root.right != null)
lastNode = InOrder(root.right);
}
return lastNode;
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
InOrder(pRootOfTree);
lastNode.right = null;
while(pRootOfTree.left != null){
pRootOfTree = pRootOfTree.left;
}
return pRootOfTree;
}
}
在中序遍历中,当遍历到根节点的时候,说明左子树已经完成了所需的操作。这句话理解很重要。
———————————————–两次分割线—————————————————————
1、
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.ArrayList;
public class Solution {
ArrayList<TreeNode> list = new ArrayList();
public void InOrder(TreeNode pRootOfTree){
if(pRootOfTree != null){
InOrder(pRootOfTree.left);
list.add(pRootOfTree);
InOrder(pRootOfTree.right);
}
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
InOrder(pRootOfTree);
list.get(0).left = null;
if(list.size() > 1){
list.get(0).right = list.get(1);
list.get(list.size() - 1).left = list.get(list.size() - 2);
}else{
list.get(0).right = null;
//
}
list.get(list.size() - 1).right = null;
for(int i=1;i<list.size()-1;i++){
list.get(i).left = list.get(i-1);
list.get(i).right = list.get(i+1);
}
return list.get(0);
}
}
二叉树的中序遍历得到的是一个有序的序列!记录中序遍历过程中遍历的结点,即是一个排序的结点,然后给这些结点添加上前后指针即可!
2、使用在中序遍历的递归一次性的完成指针的修改
注:返回的这个lastNode是以当前节点为根的树的中序遍历访问的最后一个节点。
import java.util.ArrayList;
public class Solution {
public TreeNode InOrder(TreeNode root,TreeNode lastNode){
if(root == null)
return null;
TreeNode current = root;
if(current.left != null)
lastNode = InOrder(current.left,lastNode);
current.left = lastNode;
if(lastNode != null)
lastNode.right = current;
lastNode = current;
if(current.right != null)
lastNode = InOrder(current.right,lastNode);
return lastNode;
}
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
TreeNode lastNode = null;
lastNode = InOrder(pRootOfTree,lastNode);
while(lastNode.left != null){
lastNode = lastNode.left;
}
return lastNode;
}
}
在中序遍历中,当遍历到根节点的时候,说明左子树已经完成了所需的操作。所以设置一个lastNode作为左子树中的最后一个结点,只要当前结点的左子树不为空,那么对左子树进行递归,=找最左的那个孩子,【>最左边的结点的上一个结点为null,即设置它的左指针指向lastNode;如果此时的lastNode不为null,那么就设置lastNode的右指针指向当前结点(其实就是遍历到那个点的时候,设置和它相关的双向指针)】;于是最左边的这个结点访问结束;
补充:其实,这时候相当于左孩子访问完毕(左子树完成了所需的操作),那么应该开始访问当前节点了,让lastNode指向这个结点,作为另一个结点的上一个还有另一个方向;然后轮到右孩子:右孩子会再次进行上面的循环,先找到右孩子最左的节点,无非这个时候最左的节点有了上一个节点。。。。
3、非递归的做法:
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
Stack<TreeNode> stack = new Stack();
TreeNode p = pRootOfTree;
TreeNode lastNode = null;
while(!stack.isEmpty() || p != null){
while(p != null){
stack.push(p);
p = p.left;
}
p = stack.pop();
p.left = lastNode;
if(lastNode != null)
lastNode.right = p;
lastNode = p;
p = p.right;
}
while(pRootOfTree.left != null){
pRootOfTree = pRootOfTree.left;
}
return pRootOfTree;
}
}
这里先回顾一下中序遍历(非递归),首先申请一个变量P指向根节点,然后剩余的都在while循环中进行了!注意只有这一个地方压入栈操作,把节点的左孩子的左孩子的直到null。。。。。压入栈中;当这时候p为null的时候,出栈,其实就意味着回溯了,回到当前null的父节点,设为p,对p进行一系列的操作,然后令p指向它的右孩子。(注意这里只是把p指向了它的右孩子,没有入栈操作,入栈操作只有一次)
其实,中序遍历这么个写法也对,就是把while循环中的那个while变为if-else,不过我不习惯这种写法,知道就好,还是使用上面那种while的写法,不过下面的这种if的形式便于理解:
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
Stack<TreeNode> stack = new Stack();
TreeNode p = pRootOfTree;
TreeNode lastNode = null;
while(!stack.isEmpty() || p != null){
if(p != null){
stack.push(p);
p = p.left;
}else{
p = stack.pop();
p.left = lastNode;
if(lastNode != null)
lastNode.right = p;
lastNode = p;
p = p.right;
}
}
while(pRootOfTree.left != null){
pRootOfTree = pRootOfTree.left;
}
return pRootOfTree;
}
在递归方法中,注意那个lastNode = InOrder(current.right,lastNode);
一定要有返回值,否则会报错!null
注意,在递归的这种情况一定要写返回值@!!@!
参考:
https://www.cnblogs.com/keedor/p/4467040.html
http://blog.sina.com.cn/s/blog_911243f30102wvfi.html
https://blog.csdn.net/lilianforever/article/details/51853960