题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。


思路1:

中序遍历的结果就是有序的,因此第1个方法就是考虑将中序遍历的结果记录,然后使之成为双向链表。这里的数据结构可以选择数组或者链表。以数组为例,将中序每遍历一个节点时,就添加到数组中,遍历完后数组就是有序的,然后再遍历一次数组,每遍历数组中的一个元素,就用把这个元素和下一个元素连起来。

写法1:使用数组的递归写法

import java.util.ArrayList;
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree==null)
            return null;
       ArrayList<TreeNode> list=new ArrayList<>();
       Convert(list,pRootOfTree);//遍历
       return Convert(list);//遍历结束后调用连成链表的方法
    }

    public void Convert(ArrayList<TreeNode>list,TreeNode root){
        if (root!=null){
            Convert(list,root.left);
            list.add(root);//在左边遍历结束后,即将进入右边的调用时,将当前节点记录
//对中序遍历进行操作一般都在中间
            Convert(list,root.right);
        }
    }

    public TreeNode Convert(ArrayList<TreeNode> list){
        TreeNode head=list.get(0);
        TreeNode cur=head;
        for (int i=1;i<list.size();++i){//list集合没有length方法
            TreeNode node=list.get(i);
            node.left=cur;
            cur.right=node;
            cur=node;
        }
        return head;
    }
}
//之前的list是根据值的大小将所有的节点连了起来,但是每个节点自己的left和right并没有记录
//因此需要重新连接。

写法2:使用数组的非递归写法

由于是深度搜索,所以需要使用栈。层次遍历是队列。而前中后序遍历都属于深度遍历。

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree==null)
            return null;
       ArrayList<TreeNode> list = new ArrayList<>();
       Stack<TreeNode> stack = new Stack<>();

        while(pRootOfTree!=null || stack.size()!= 0){
           if(pRootOfTree!=null ){//先遍历左子树
               stack.push(pRootOfTree);
               pRootOfTree = pRootOfTree.left;
           }else{//然后在遍历右子树前,将节点添加到list中
               list.add(stack.peek());
               pRootOfTree = stack.pop().right;
           }        
        }
       return Convert(list);
    }

    public TreeNode Convert(ArrayList<TreeNode> list){
        TreeNode head=list.get(0);
        TreeNode cur=head;
        for (int i=1;i<list.size();++i){
            TreeNode node=list.get(i);
            node.left=cur;
            cur.right=node;
            cur=node;
        }
        return head;
    }
}

思路2:

优化:之前是将排序好的list再串起来,那么考虑能否在遍历的同时,就把线给连上,遍历结束之时,链表就连接完成。同时还能不用额外的空间。

写法1:使用递归

public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null)
            return pRootOfTree;
        TreeNode pre = null;//当前节点的上一个节点
        pre = convertHelper(pRootOfTree,pre);
        TreeNode head = pre;
        while(head.left != null) {
            head = head.left;
        }  
        return head;
    }
    //将以node为根结点的树转化为排序链表,链表头部与pre链接,并返回最后一个结点
    private TreeNode convertHelper(TreeNode node,TreeNode pre) {
        //处理左子树,获得最大结点
        if(node.left!=null)
            pre = convertHelper(node.left, pre);
        //链接最大结点和根结点
        node.left = pre;
        if(pre!=null)
            pre.right = node;
        //处理右子树
        pre = node;
        if(node.right!=null)
            pre = convertHelper(node.right, pre);
        return pre;
    }
}

写法2:非递归

这种写法就需要一个变量,保存上一个节点。这样才能在遍历某个节点时,连起来。在最左边的叶子节点被遍历时,进行连接操作。

import java.util.Stack;
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null)
            return null;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode head = null;//head只是记录双向链表的首个节点
        TreeNode pre = null;// 用于保存中序遍历序列的上一节点

        while(pRootOfTree!=null||!stack.isEmpty()){
             if(pRootOfTree != null) {
                stack.push(pRootOfTree);
                pRootOfTree = pRootOfTree.left;
            } else {
                pRootOfTree = stack.pop();//此时左子树遍历完了,弹出栈顶元素
                if(pre == null){ //pre初始值是null
                    pre = pRootOfTree;//因此将弹出的第一个节点,也就是序列的最左边的树赋值
                    head = pRootOfTree;
                  }      
                else {//进入这个else时,说明至少是第2个节点,此时进行连接。
                    pre.right = pRootOfTree;
                    pRootOfTree.left = pre;
                    pre = pRootOfTree;
                }
                pRootOfTree = pRootOfTree.right;//遍历完左子树后,应该向右走了
            }
        }
        return head;
    }
}

总结:使用数组或者队列的方式,容易想到,思路2反而不容易想到,思路2的难点在于,找到合适的连接时机。
尤其是递归的时候。