剑指(26):二叉搜索树与双向链表

描述:将一个二叉搜索树转为一个双向链表,要求不能创建新节点,只能调整树中节点指针的指向;

解题思路分析

  • 递归:

    • 递归的整体把握更好,将整个二叉树抽象为一个三节点的二叉树
    • 我们只需要关心两点:
      • 第一点:整个链表的第一个节点在哪里 ?
        • 找到第一点好解决,递归调用当前方法,输入参数为 root.left ,直达最左侧的叶子节点
      • 第二点:在任何局部如何互连 ?:左-根-右
        • 如何互连?关键点在于一个全局指针 last
        • 我们每次往链表上连上一个点,就把last指针指向当前链表的最后一点
        • 这样的好处就是,不管当前互连进行到那个局部,我们总能知道我们链表的最后一个节点
        • 这样我们就非常方便的往链表后面添加互连新的节点
    //伪代码
    // 1、找到左孩子,连接 root 
        TreeNode  left  =  convert(roo.left);
    	last=left;  			// last首先指向左孩子
    	last.right = root ;		// 左孩子指向根
        root.left  = last ;		// 根指向左孩子 完成互连
    
    // 2、last 指向 root  ,因为此时root已经是链表的最后
        last = root ; 
    
    // 3、 同第一步一样,连接右孩子
    	TreeNode  right  =  convert(roo.right);
    	root.right = right ;   // root 指向 右孩子
        right.left  = last ;   // 右孩子 指向 左孩子
    	last = right ;		   // last 指向 右孩子 (当前链表的最后一个节点)	
    
    
  • 非递归:

    • 非递归的方法实际就是 中序遍历 的过程

      • 沿着左侧分支一直下行,将沿途的节点压入栈中

      • 依次弹出栈顶的元素,进行操作:

        • 若为最左侧的点,即第一个节点,用 root 指针保存 , 同时也用pre指针指向它

        • 从第二个节点开始:弹栈 - 互连 - 遍历右孩子(如果存在)

          • cur:表示当前节点 , pre:链表中的最后一个节点

          • 	//弹栈
            	Node cur  = stack.pop();	
            	//互连
            	pre.right = cur ;
            	cur.left  = pre ;
            	pre = cur ;	  	//同递归一样,每次互连后,pre 都向后移动
            	//遍历右孩子
            	cur = cur.right ;
            

            围绕以上这几个步骤,反复的循环!直到栈空

1、非递归法

//节点类
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }
}

import java.util.*;
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        //非递归法
        //退化情况,节点为空
        if(pRootOfTree == null ) return null;
        //创建一个辅助栈
        Stack<TreeNode> stack= new Stack<TreeNode>();
        //将引用cur指向root,pre指向遍历节点的的前一个节点,开始为null
        TreeNode cur = pRootOfTree ;
        TreeNode pre = null ;
        Boolean flag = true ; //用于控制流程 ,找到第一个中序遍历节点
        while(cur != null || !stack.isEmpty()){
              while( cur != null ){
                //一直下行,直到找到最左侧节点,每遍历一个就入栈,最后入栈的即为中序遍历第一点
               stack.push(cur);
               cur = cur.left ;
            }
            	//每次弹出一个节点
               cur = stack.pop();
            if(flag){
                pRootOfTree = cur;   //将第一个节点节点用 pRootOfTree 保存:用于最后返回
                pre  = pRootOfTree;  //同时将pre也指向当前第一节点:使用pre进行后移
                flag = false ;		 //找到后修改标记
            }else{
                pre.right = cur ; // 将当前的 pre 的后继指向 cur
                cur.left = pre ;  // 将cur 的前驱指向 pre
                pre = cur ;       //将 pre指针指向 下一个连接上的节点 
            }
            // 栈中保存的广义上来说
            // 都是每一层次的左孩子同时也是 root 节点,所以在遍历往后
          	/*
 							4          栈中保存的是:(底)【4,2,1					
 						2        5    		所以先弹出 左孩子1,然后1没有右孩子
          			1		3        6		下一个弹出2(root),即 1(left) 的 父亲 ,然后再访问root 的3(Right)
											以上就是一个局部的中序遍历:1 -> 2 -> 3
          	*/
        	// 所以应该访问其右孩子,才满足: 左->根->右
             cur = cur.right ;  
        }
        
        return  pRootOfTree ;

    }
}

2、递归法

public class Solution {
    //定义一个全局指针,指向当前已经形成的双向两边的最后一个节点
    TreeNode last = null;
    public TreeNode Convert(TreeNode root) {
        if(root == null ) return null;
        //当前节点的左右孩子都为空,返回当前节点
        if(root.left == null && root.right == null ){
            last=root;
            return root;
        }
        //1.将当前节点的左子树构成双向链表
        TreeNode left = Convert(root.left);
        if(left!=null){
            last.right=root;//将last的后继指向root
            root.left=last; //将root的前驱指向last
        }
            last=root;     // 将last指向root,即当前已连接链表的最后一个
       //2.将当前节点的右子树构成双向链表
        TreeNode right = Convert(root.right); 
        //注意要,调用了convert方法之后,只要right不为空,全局last指针会改变,指向right
        //所以在下面的连接中,要用root与right相连
        //即 left-> root(last在这里)
        //再 left-> root->right(last在这里)
        if(right!=null){
            root.right=right;//将last的后继指向right
            right.left=root; //将right的前驱指向last
        }
        return left!=null?left:root;  //最终返回:有左链,返回左链,否则返回root  
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值