11.24总结二叉树

本文详细介绍了如何将二叉搜索树转换为有序链表,以及如何通过前序、中序和后续遍历构建和遍历二叉树。提供了非递归和递归两种方法,涉及中序遍历、前序遍历和后续遍历的转换。同时,还讨论了排序子序列的问题和二叉树的不同遍历方式,包括前序、中序和后续遍历的非递归实现。
摘要由CSDN通过智能技术生成

目录

一.将二叉搜索树变成有序链表

二.从前序遍历和中序遍历构建二叉树

三.从中序遍历和后续遍历创建字符串

四.二叉树创立字符串

五.订正题目

六.排序子序列

七.二叉树非递归遍历

1.前序遍历

3.后续遍历


一.将二叉搜索树变成有序链表

我们的思路就是因为一颗二叉搜索树所以中序遍历是有序的,从小到大 排的.

所以我们的思路就是一边遍历一边修改节点指向.因为是双向链表,就把left和right修改指向

所以再需要打印那一步我们开始搞事情,我们需要对他修改指向,根据上图所示,我们最好就是

left改为下一个的.right改为上一个的.

我们就从第一个走到根节点这一步开始思考

如果是第一个那么就是左子树最左边的树,那么他的下一个就是null.上一个就是他的根节点.

然后回推到他的下一个,他的下一个就是刚刚那个节点,上一个就是他的右子树.

所以我们需要有一颗后继节点.记录上一个节点的位置.

并且这个节点得是成员变量.不然每次递归,他都会重置

这里我们发现,root指向了prev.但是prev并没有连接到root.其实prev的right就是root啊

所以我们需要在他移向root之前让他指向cur;

根据如此递归,递归完成的时候,就变成了双向链表,就到最后一个节点的时候,因为prev还是和他连接的,他的左节点连接的是prev.他的右节点本来就是null.所以没毛病

那就回到了主函数,

我们现在虽然得到的是主函数,但是root还是指向的是左节点,我们需要一个头结点

就开始迭代找.或者定义一个头结点,当prev为null的时候满足

方法一:非递归版
解题思路:
1.核心是中序遍历的非递归算法。
2.修改当前遍历节点与前一遍历节点的指针指向。
    import java.util.Stack;
    public TreeNode ConvertBSTToBiList(TreeNode root) {
    if(root==null)
    return null;
    Stack<TreeNode> stack = new Stack<TreeNode>();
    TreeNode p = root;
    TreeNode pre = null;// 用于保存中序遍历序列的上一节点
    boolean isFirst = true;
    while(p!=null||!stack.isEmpty()){
    while(p!=null){
    stack.push(p);
    p = p.left;
    }
    p = stack.pop();
    if(isFirst){
    root = p;// 将中序遍历序列中的第一个节点记为root
    pre = root;
    isFirst = false;
    }else{
    pre.right = p;
    p.left = pre;
    pre = p;
    }
    p = p.right;
    }
    return root;
    }
方法二:递归版
解题思路:
1.将左子树构造成双链表,并返回链表头节点。
2.定位至左子树双链表最后一个节点。
3.如果左子树链表不为空的话,将当前root追加到左子树链表。
4.将右子树构造成双链表,并返回链表头节点。
5.如果右子树链表不为空的话,将该链表追加到root节点之后。
6.根据左子树链表是否为空确定返回的节点。
    public TreeNode Convert(TreeNode root) {
    if(root==null)
    return null;
    if(root.left==null&&root.right==null)
    return root;
    // 1.将左子树构造成双链表,并返回链表头节点
    TreeNode left = Convert(root.left);
    TreeNode p = left;
    // 2.定位至左子树双链表最后一个节点
    while(p!=null&&p.right!=null){
    p = p.right;
    }
    // 3.如果左子树链表不为空的话,将当前root追加到左子树链表
    if(left!=null){
    p.right = root;
    root.left = p;
    }
    // 4.将右子树构造成双链表,并返回链表头节点
    TreeNode right = Convert(root.right);
    // 5.如果右子树链表不为空的话,将该链表追加到root节点之后
    if(right!=null){
    right.left = root;
    root.right = right;
    }
return left!=null?left:root;        
    }
方法三:改进递归版
解题思路:
思路与方法二中的递归版一致,仅对第2点中的定位作了修改,新增一个全局变量记录左子树的最后一个节点。
    // 记录子树链表的最后一个节点,终结点只可能为只含左子树的非叶节点与叶节点
    protected TreeNode leftLast = null;
    public TreeNode Convert(TreeNode root) {
    if(root==null)
    return null;
    if(root.left==null&&root.right==null){
    leftLast = root;// 最后的一个节点可能为最右侧的叶节点
    return root;
    }
    // 1.将左子树构造成双链表,并返回链表头节点
    TreeNode left = Convert(root.left);
    // 3.如果左子树链表不为空的话,将当前root追加到左子树链表
    if(left!=null){
    leftLast.right = root;
    root.left = leftLast;
    }
    leftLast = root;// 当根节点只含左子树时,则该根节点为最后一个节点
    // 4.将右子树构造成双链表,并返回链表头节点
    TreeNode right = Convert(root.right);
    // 5.如果右子树链表不为空的话,将该链表追加到root节点之后
    if(right!=null){
    right.left = root;
    root.right = right;
    }
return left!=null?left:root;        
    }

二.从前序遍历和中序遍历构建二叉树

因为前序遍历是先根遍历,可以通过前序找到根节点,然后在中序遍历这里找到左子树和右子树

再再左子树和右子树分别找前序遍历的根,如此递归,

但是会发现,每次递归左子树和右子树对应的数组会变小,这里我认为也不需要分隔数组,就只要改变数组指向就好

第一步,在前序数组开始遍历,没遍历一个元素,就去中序遍历循环找对应的元素下标

第二步开始分隔中序数组,左边的是左子树右边的是右子树,

定义两个指针来指向左子树和右子树开头和结尾.

我这里用迭代的方式做,出现了问题一组循环,多组加,就导致可能数组越界

所以应该还是得试试递归的方式

因为考虑到题干中给的函数只有两个数组变量.所以我们另外再建立一个方法,添加指针变量

这里我没有考虑完全.如果是到了空节点,就直接递归回去了.就不需要在前序数组再往前一位,

要先判断

我这里出现了一个及其离谱的错误,左右树创建的时候写反了

这里的思路就是先判断左指针是否大于右指针.

因为每次递归都会找每颗子树自己的根.就会导致指针越来越接近

直到小于或者大于,就说明

已经递归到自身已经不能再往下了,就说明到了null了,所以要给递归弄一个结束条件,也就是这个条件

然后创建每次递归找到的根节点,就以前序遍历为主

再从中序遍历找到对应的下标,把中序数组以下标为界限左右各分为左子树右子树,

再对这次递归的节点的左节点和右节点分别来创建再次进行递归

因为这次递归的节点的左子树的节点又是一个新的根节点,所以就缩小范围按之前数组的界限

就好

class Solution {
    public int findIndex(int[] inorder,int key,int ib,int ie){
        for(int i=ib;i<=ie;i++){
            if(inorder[i]==key){
                return i;
            }
        }
        return -1;


    }
    public int preIndex=0;//防止每次递归都会变
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder==null||inorder==null) return null;
        return  createTree(preorder,inorder,0,inorder.length-1);
    }
    public TreeNode createTree(int[] preorder,int[] inorder,int inbegin,int inend){
        if(inbegin>inend){
            return null;
        }
        //满足这个条件说明没有左树或者右树了
        TreeNode root=new TreeNode(preorder[preIndex]);
        int index=findIndex(inorder,preorder[preIndex],inbegin,inend);//找到根在中序遍历的位置
        if(index==-1) return null;
        preIndex++;
        //递归在各自的左树右树找相应的根节点
        root.left=createTree(preorder,inorder,inbegin,index-1);
        root.right=createTree(preorder,inorder,index+1,inend);
        return root;

    }
}

三.从中序遍历和后续遍历创建字符串

原理跟上题一样,唯一要改变的就是从后往前遍历,

这里发现因为要从后往前遍历,就要把遍历的指针初始为前序数组末尾,

但是不能放在外面因为数组定义在方法外

这里我们的处理方法,就是还是在外面初始化,然后再函数内设为数组末尾就好了

但是还是要注意.因为后续遍历,是左右根.如果从后往前看,就是根右子树,左子树,所以应该是先建立右子树再建立左子树.

class Solution {
     int preIndex=0;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder==null||postorder==null) return null;
         preIndex=postorder.length-1;
        return createTree(inorder,postorder,0,inorder.length-1); 

    }
    public TreeNode createTree(int[]inorder,int[]postorder,int ib,int ie){
        if(ib>ie) return null;

        TreeNode root=new TreeNode(postorder[preIndex]);
        int index=findIndex(inorder,ib,ie,postorder[preIndex]);
        if(index==-1) return null;
        preIndex--;
        root.right=createTree(inorder,postorder,index+1,ie);
        root.left=createTree(inorder,postorder,ib,index-1);
        return root;

    }
    public int findIndex(int[] inorder,int ib,int ie,int key){
        for(int i =ib;i<=ie;i++){
            if(inorder[i]==key){
                return i;
            }
        }
        return -1;
    }
}

四.二叉树创立字符串

这题很简单,从根开始,遇到左子树开始加个左括号,加上左子树的根,再往下遍历,一个根左右子树遍历完加一个括号,再开始遍历右子树

这里还有一个注意点,如果左子树不是空,但是右子树是空,是不加括号的,

但是如果左子树是空,右子树不是空就加括号.

这里我们重新建立一个方法.因为题目要求string.但是这题涉及到拼接,所以我们在另外一个方法用sb拼接完了,再回到函数tostring就可以

class Solution {
    
    public String tree2str(TreeNode root) {
        if(root==null) return null;
        StringBuilder sb=new StringBuilder();//相当于全局变量.不会变
        treetoString(root,sb);
        return sb.toString();
    }
    public void treetoString(TreeNode root,StringBuilder sb){
        if(root==null) return;
        sb.append(root.val);//不管是根节点还是之后的都可以直接加,因为之后的再地柜之前已经加上了括号

        if(root.left!=null){
            sb.append('(');
            treetoString(root.left,sb);
            sb.append(')');
        }else{
            if(root.right==null){
                return;//左节点为空右节点为空的情况不用考虑
            }else{
                sb.append("()");//左节点为空,右节点为空,根据题意需要加上括号
            }
        }
        if(root.right==null){
            //这种情况就是左节点为null.且右节点也为null的 情况
            return;
        }else{
            //这种情况就是左节点不为null,且右节点也不为null的情况
             sb.append('(');
            treetoString(root.right,sb);
            sb.append(')');
        }
    

五.订正题目

普通方法里不能有静态变量.静态方法也不能有静态变量,静态修饰的属于类变量,

abstract不能修饰字段

对成员变量的赋值必须放在方法的内部

六.排序子序列

非递增就是 递减但是有连续相同的元素或者只有递减

所以这道题我们的思路就是先接收

然后判断,是否一直递减就为一组,有相同的,就继续往后加

import java.util.*;
public class Main{
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int[] array=new int[n+1];
        for(int i=0;i<n;i++){
            array[i]=sc.nextInt();
        }//所有数据接收完整开始判断
        int  count=0;
        int i=0;
        while(i<n){//不能用for循环后续会有循环
            if(i<n&&array[i]<array[i+1]){
                while(array[i]<=array[i+1]){
                    i++;
                }
                count++;i++;//到另外一组了
            }else if(array[i]==array[i+1]){//每一组开头有可能是相同的
                i++;
            }else{
                 while(i<n&&array[i]>=array[i+1]){
                    i++;
                }
                count++;i++;//到另外一组了
            }
            
        }
        System.out.print(count);
    }
}

这种情况就算遇到相同的,就把归为下一组了.而我的处理就是也属于这一组.都没有毛病

七.二叉树非递归遍历

1.前序遍历

走到这一步就可能会卡.因为左边遍历完,那么右边怎么办

所以这里就展现出栈的好处了.可以到最底层的时候,cur就变成了null.就开始往回弹出元素,并打印

所以 需要再嵌套一个循环

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<>();
        Stack<TreeNode> stack=new Stack<>();
        TreeNode cur=root;
        while(cur!=null||!stack.isEmpty()){
            while(cur!=null){
                 list.add(cur.val);
                stack.push(cur);
                cur=cur.left;
            }
            TreeNode top=stack.pop();
          //  list.add(top.val);
            cur=top.right;
        }
        return list;
    }
}

但是有一种情况就是如果cur走到底层的右边还是null.就会跳出循环

所以循环的终止条件应该加一条栈是否为空,如果栈也为空了.就说明真的终止了

2.中序遍历

中序遍历根前序类似

只要更改打印位置即可.让他走到最左边的时候打印.然后栈往回弹的时候分别打印,再判断右是否有,有的话就打印,没有直接往回弹;

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<>();
        Stack<TreeNode> stack=new Stack<>();
        while(root!=null||!stack.isEmpty()){
            while(root!=null){
                stack.add(root);
                root=root.left;
            }
           TreeNode top=stack.pop();
           list.add(top.val);
           if(top.right!=null){
               root=top.right;
           }
        }
        return list;

    }
}

3.后续遍历

后序比较麻烦,因为如果按之前的改一下打印顺序

就会在一种情况下进行死循环

所以我们要判断是否遍历过了.就算右边是空的,但是遍历过了,就直接往后弹了

就需要建立一个后继节点来记录上次弹的元素

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<>();
        Stack<TreeNode> stack=new Stack<>();
        TreeNode prev=null;
        while(root!=null||!stack.isEmpty()){
            while(root!=null){
                stack.push(root);
                root=root.left;
            }
           TreeNode top=stack.peek();
           if(prev==top.right||top.right==null){
                stack.pop();
               list.add(top.val);
              prev=top;
           }else{
              root=top.right;
           }
        }
        return list;

    }
    
}

所以每次判断右边的时候如果满足虽然不为空,但是遍历过了.就继继续弹,所以这里就需要一个后继节点,记录弹出的元素.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值