11.24总结二叉树

news2025/8/8 20:24:05

目录

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

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

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

四.二叉树创立字符串

五.订正题目

六.排序子序列

七.二叉树非递归遍历

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;

    }
    
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/34246.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何通过短链接跳转到小程序,或者跳转至小程序webview一个h5页面

theme: channing-cyan 一、需求背景&#xff1a; 公司需要通过发送短信携带短链接&#xff0c;用户点击短链接跳转到小程序的某个页面&#xff0c;然后打开小程序该页面webviewh5页面&#xff0c;然后链接携带参数。 使用技术&#xff1a; 主要是用小程序的云开发&#x…

HashMap为什么会发生死循环?

Java的HashMap是线程不安全的&#xff0c;所以在jdk1.7中&#xff0c;多线程的HashMap扩容采用头插法会发生死循环问题。为什么会发生这种情况呢&#xff1f; 正常扩容 当我们向HashMap中添加值的时候&#xff0c;调用的是Put()方法。 public V put(K key, V value) {//如果…

使用Prometheus监控docker compose方式部署的ES

需求 收集 ES 的指标, 并进行展示和告警; 现状 ES 通过 docker compose 安装所在环境的 K8S 集群有 Prometheus 和 AlertManager 及 Grafana 方案 复用现有的监控体系, 通过: Prometheus 监控 ES. 具体实现为: 采集端 elasticsearch_exporter 可以监控的指标为: NameTy…

使用 TensorFlow 构建计算机视觉模型

什么是计算机视觉&#xff1f; 计算机视觉 (CV) 是现代人工智能 (AI) 和机器学习 (ML) 系统的主要任务。它正在加速行业中的几乎每个领域&#xff0c;使组织能够彻底改变机器和业务系统的工作方式。 在学术上&#xff0c;它是计算机科学的一个成熟领域&#xff0c;数十年的研…

异常(Exception)

随着面向对象的结束&#xff0c;我们的JavaSE也就接近了尾声&#xff0c;还有两个章节没有去梳理&#xff0c;常用类和异常&#xff0c;本章先讲异常&#xff0c;剩下的常用类后面再来补。 废话不多说&#xff0c;直接开始本章的内容。 1. 认识异常 引出&#xff1a; 假设 n…

数据结构与算法_二叉树(BST树)_面试题总结

这篇笔记记录二叉树相关的常考题。 1 BST树区间元素搜索问题 **解决方法&#xff1a;**利用BST树的中序遍历&#xff0c;中序遍历后输出的是从小到大的顺序。 // 求满足区间的元素值 [i,j];void findValues(vector<T> &vec, int i, int j){// 封装一个递归接口 fin…

精华推荐 | 【深入浅出RocketMQ原理及实战】「性能原理挖掘系列」透彻剖析贯穿RocketMQ的系统服务底层原理以及高性能存储设计挖掘深入

设计背景 消息中间件的本身定义来考虑&#xff0c;应该尽量减少对于外部第三方中间件的依赖。一般来说依赖的外部系统越多&#xff0c;也会使得本身的设计越复杂&#xff0c;采用文件系统作为消息存储的方式。 RocketMQ存储机制 消息中间件的存储一般都是利用磁盘&#xff0…

基于node.js的学生管理系统设计

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1选题背景和意义 1 1.1.1选题背景 1 1.1.2选题意义 1 1.2国内外研究现状、发展动态 2 1.2.1国内研究现状 2 1.2.2国外研究现状 3 1.2.3发展动态 3 1.3研究内容 4 第2章 Node.js软件说明 5 2.1 Node.js概述 5 2.2 Node.js的模块 6 2.3…

语义信息概述

语义信息概述 什么叫语义信息 无论在图像&#xff0c;文本&#xff0c;语音处理领域等&#xff0c;我们常看到一个词&#xff0c;“语义信息”。&#xff08;有意义的数据提供的信息&#xff09; 维基百科中的解释&#xff1a; 语义信息&#xff08;英语&#xff1a;semantic…

【基础算法Ⅰ】算法入门篇

目录 进入算法世界 1.输入输出 1.1输入输出 1.2快读 2.位运算 2.1运算符 2.2位运算 3.枚举 3.1枚举的引入 3.2枚举的简单理解 3.3枚举简介 3.4 枚举算法实例 算法复杂度 时间复杂度 进入算法世界 瑞士著名的科学家Niklaus Wirth教授曾提出&#xff1a;数据结构算…

在C#方法中 out、ref、in、params 关键字的小结

out&#xff1a;关键字&#xff1a; 指定的参数在进入函数时会清空自己&#xff0c;必须在函数内部赋初值 ref关键字&#xff1a; 指定的参数必须在进入函数时赋初值&#xff0c;在函数内部可以重新赋值 In关键字&#xff1a; 指定的参数必须在进入函数时赋初值&#xff0c;…

C++入门教程||C++while循环

whlie 语法 C 中 while 循环的语法&#xff1a; while(condition) {statement(s); } 在这里&#xff0c;statement(s) 可以是一个单独的语句&#xff0c;也可以是几个语句组成的代码块。condition 可以是任意的表达式&#xff0c;当为任意非零值时都为真。当条件为真时执行…

Java.md

sa一、基础篇 网络基础 TCP三次握手 1、OSI与TCP/IP 模型2、常见网络服务分层3、TCP与UDP区别及场景4、TCP滑动窗口&#xff0c;拥塞控制5、TCP粘包原因和解决方法6、TCP、UDP报文格式 HTTP协议 1、HTTP协议1.0_1.1_2.02、HTTP与HTTPS之间的区别3、Get和Post请求区别4、HTTP常见…

Python实现BP神经网络ANN单隐层回归模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 20世纪80年代中期&#xff0c;David Runelhart。Geoffrey Hinton和Ronald W-llians、DavidParker等人分…

SpringCloud 组件Gateway服务网关【gateway快速入门】

目录 1&#xff1a;Gateway服务网关 1.1&#xff1a;为什么需要网关 1.2&#xff1a;gateway快速入门 1&#xff09;&#xff1a;创建gateway服务&#xff0c;引入依赖 2&#xff09;&#xff1a;编写启动类 3&#xff09;&#xff1a;编写基础配置和路由规则 4&#xf…

啥牌子的无线蓝牙耳机好用?无线蓝牙耳机推荐2022

蓝牙耳机这几年技术越好越高&#xff0c;其最大的魅力就是随时随地听音乐&#xff0c;无论是上下班还是日常使用&#xff0c;出门携带也方便&#xff0c;市面上的蓝牙耳机众多&#xff0c;很多人不知道该如何选择&#xff0c;下面整理了几款音质清晰&#xff0c;综合性能优秀的…

分享25个JSP源码,总有一款适合您

链接&#xff1a;https://pan.baidu.com/s/17ug7A_b2nHgu-x1K-GIVlQ?pwd6367 提取码&#xff1a;6367 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c;大家下载后可以看到。 renren-security轻量级权限管理系统 renr…

Linux C应用编程-1-文件IO

1.open与close #include <stdio.h> //IO操作需要包含的头文件 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>char filename[] "text.txt";int main(void) {int fd;fd open(filename, O_RD…

systemd 252 如预期的锁定了 Linux 引导过程

导读今天给大家介绍一下systemd 252锁定 Linux 引导过程systemd 252 如预期的锁定了 Linux 引导过程 之前&#xff0c;我们 报道 过&#xff0c;systemd 创始人发文指出 Linux 引导过程不安全&#xff0c;并提出采用加密签名的统一内核镜像&#xff08;UKI&#xff09;&#x…

SA实战 · 《SpringCloud Alibaba实战》第02章-专栏设计

作者:冰河 星球:http://m6z.cn/6aeFbs 博客:https://binghe001.github.io 文章汇总:https://binghe001.github.io/md/all/all.html 大家好,我是冰河~~ 从今天开始,我们正式进入《SpringCloud Alibaba实战》专栏的学习,在《开篇》一文中,我们大体介绍了整个专栏的结构安…