【数据结构】二叉树的前序遍历(七)

2023-09-21 08:55:21

 题目:二叉树的前序遍历

题目详情:给你二叉树的根节点 root ,返回它节点值的 前序 遍历;

我们先来看几个示例:

输入:root = [ 1,null,2,3 ]

输出:[ 1,2,3 ]

 示例2:

输入:root = [ 1,2 ]

输出:[ 1,2 ]

示例三:

输入:root = [  ]

输出:[  ]

提示:

树中结点数目在范围【0,100】内

-100 <= Node.val <= 100

开始分析:

通过以上的示例我们得知,这道题呢就是把一棵树用前序遍历的方式将结点的值赋给一个数组,然后返回这个数组的指针;

我们之前学过二叉树的前序遍历打印结点的值,现在是将结点的值储存起来,其实原理都一样;

这个是要实现的函数的基本信息;

int* preorderTraversal(struct TreeNode* root, int* returnSize)

思路实现:

我们首先要确定数组的大小,数组的个数等于树中结点的个数,所以我们要先计算树中结点的个数;

int TreeSize(struct TreeNode* root)
{
    return root==NULL?0:TreeSize(root->left)+TreeSize(root->right)+1;
}   
//算结点的个数
*returnSize=TreeSize(root);

这个计算树结点个数的函数之前有些过,大体思路就是树结点的总和等于 根结点本身加上左,右子树的结点个数;

然后数组的元素个数知道了就要开始申请动态空间来定义数组了;

//开辟动态空间
int* arr=(int*)malloc(sizeof(int)*(*returnSize));

直接一个 malloc 拿下,元素类型与树结点的值类型一致;

然后数组也有了我们就要对其赋值了;

易错点:

1,前序遍历是需要用递归来实现的,不能直接在主函数里递归,因为主函数里有开辟动态空间还挺大的,会造成堆溢出,所以我们需要用另外一个函数来进行赋值操作;

void _preorderTraversal(struct TreeNode* root,int* arr)
 {
     if(root==NULL)
     {
         return ;
     }
     int i=0;
     arr[i++]=root->val;
     _preorderTraversal(root->left,arr);
     _preorderTraversal(root->right,arr);
 } 
  
    //赋值
     _preorderTraversal(root,arr);

初学者们经常犯的错误,这里很明显错误的是下标 i 在递归调用函数时的不断重置,那我们把下标 i 放在主函数里是不是就可以解决呢?

void _preorderTraversal(struct TreeNode* root,int* arr,int i)
 {
     if(root==NULL)
     {
         return ;
     }
     arr[i++]=root->val;
     _preorderTraversal(root->left,arr,i);
     _preorderTraversal(root->right,arr,i);
 }   
int i=0;
//赋值
_preorderTraversal(root,arr,i);

这为什么也通过不了呢?很简单,每一次调用的下标 i 只是上一个函数的形参而已,形参的改变不会影响实参!

这有人就会问呀那也运行成功了一半呀! 那是因为能运行成功的树都是【斜树】这种树都只有一边的,我前面也介绍过;

斜树图示:

对,就是这种树才程序才可以通过,那为什么其他树不可以呢?因为像【斜树】这种每次的函数返回都是空并不涉及下标 i 的值的改变,但是其他树呢,就比如这棵树;

当函数走到 D 点的时候下标 i 为3,然后返回 B 开始给其右子树 E 赋值

此时 E 中下标 i 的值是 4 吗?并不是! D 中改变 i 的值出了函数就失效了形参的改变不能影响实参,所以此时 E 中下标 i 的值还是 2 ,因此程序通过不了了;

所以既然传值不行,那我们就传地址嘛,这样下标 i 就可以灵活变通了;

void _preorderTraversal(struct TreeNode* root,int* arr,int* i)
 {
     if(root==NULL)
     {
         return ;
     }
     arr[(*i)++]=root->val;
     _preorderTraversal(root->left,arr,i);
     _preorderTraversal(root->right,arr,i);
 }  

 int i=0;
 //赋值
  _preorderTraversal(root,arr,&i);

这样就ok了,还有人说用全局变量也行,那我们来看看;

int i=0;
void _preorderTraversal(struct TreeNode* root,int* arr)
 {
     if(root==NULL)
     {
         return ;
     }
     arr[i++]=root->val;
     _preorderTraversal(root->left,arr);
     _preorderTraversal(root->right,arr);
 }    

//赋值
 _preorderTraversal(root,arr);

大家忽略了一个问题,这种方式只能是一次性的;

因为全局变量 i 的值会一直变化且保存的,而要通过的话是要进行大量测试的,要调用很多次函数而每一次调用函数下标 i 的值都是在上个函数里调用过的值,并没有重置,所以调用多了下标 i 就会无限大就会越界访问了;

源代码:

void _preorderTraversal(struct TreeNode* root,int* arr,int* i)
 {
     if(root==NULL)
     {
         return ;
     }
     arr[(*i)++]=root->val;
     _preorderTraversal(root->left,arr,i);
     _preorderTraversal(root->right,arr,i);
 }

int TreeSize(struct TreeNode* root)
{
    return root==NULL?0:TreeSize(root->left)+TreeSize(root->right)+1;
}

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    //算结点的个数
    *returnSize=TreeSize(root);
    //开辟动态空间
    int* arr=(int*)malloc(sizeof(int)*(*returnSize));
    int i=0;
    //赋值
     _preorderTraversal(root,arr,&i);
     return arr;
}

这就是这道题的解题思路以及易错点了,写程序还是得细心得全面,特别是递归问题考虑的东西就更多了;

这阶段也还是带大家刷一些常见且经典的题目,掌握算法形成思路!

后面博主会陆续更新;

如有不足之处欢迎来补充交流!

完结。


更多推荐

JVM内存模型及JAVA程序运行原理

目录平台JVM简介内存结构方法区堆一个对象的内存分配流程栈局部变量表操作栈动态连接方法返回地址程序计数器Metaspace元空间本地方法栈直接内存CodeCacheJAVA程序在JVM内是如何执行的平台Java是一种可以跨平台的编程语言。Java可以跨平台得益于JVM(java虚拟机)。我们把CPU处理器与操作系统的整

springboot大学生体质测试管理系统springboot009

大家好✌!我是CZ淡陌。一名专注以理论为基础实战为主的技术博主,将再这里为大家分享优质的实战项目,本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目,希望你能有所收获,少走一些弯路,向着优秀程序员前行!🍅更多优质项目👇🏻👇🏻可点击下方获取🍅文章底部或评论区获取🍅Java项目精品实

flask要点与坑

简介Flask是一个用Python编写的Web应用程序框架,该框架简单易用、模块化、灵活性高。该笔记主要记录Flask的关键要点和容易踩坑的地方Flask日志配置Flask中的自带logger模块(也是python自带的模块),通过简单配置可以实现将日志记录到日志文件中(记录关键日志有助于以后分析问题);更详细的log

领域知识图谱的医生推荐系统:利用BERT+CRF+BiLSTM的医疗实体识别,建立医学知识图谱,建立知识问答系统

项目设计集合(人工智能方向):助力新人快速实战掌握技能、自主完成项目设计升级,提升自身的硬实力(不仅限NLP、知识图谱、计算机视觉等领域):汇总有意义的项目设计集合,助力新人快速实战掌握技能,助力用户更好利用CSDN平台,自主完成项目设计升级,提升自身的硬实力。专栏订阅:项目大全提升自身的硬实力[专栏详细介绍:项目设计

Golang goroutine MPG模式浅析

协程是通过使用关键字go调用(或执行)一个函数或者方法来实现的(也可以是匿名函数)。Go语言在语言层面上支持了并发,goroutine是Go语言提供的一种用户态线程,有时我们也称之为协程。所谓的协程,某种程度上也可以叫做轻量线程,它不由os而由应用程序创建和管理,因此使用开销较低(一般为4K)。我们可以创建很多的gor

【无公网IP内网穿透】Windows搭建Web站点

什么是cpolar?cpolar是一个非常强大的内网穿透工具,开发调试的必备利器。它可以将本地内网服务器的HTTP、HTTPS、TCP协议端口映射为公网地址端口,使得公网用户可以轻松访问您的内网服务器,无需部署至公网服务器。支持永久免费使用,无需公网IP,也无需设置路由器。概述本次教程中,我们将实现在windows上搭

第十九章、【Linux】开机流程、模块管理与Loader

19.1.1开机流程一览以个人计算机架设的Linux主机为例,当你按下电源按键后计算机硬件会主动的读取BIOS或UEFIBIOS来载入硬件信息及进行硬件系统的自我测试,之后系统会主动的去读取第一个可开机的设备(由BIOS设置的),此时就可以读入开机管理程序了。开机管理程序可以指定使用哪个核心文件来开机,并实际载入核心到

【Java系列】深入解析 Lambda表达式

序言你只管努力,其他交给时间,时间会证明一切。文章标记颜色说明:黄色:重要标题红色:用来标记结论绿色:用来标记一级论点蓝色:用来标记二级论点希望这篇文章能让你不仅有一定的收获,而且可以愉快的学习,如果有什么建议,都可以留言和我交流1基础介绍1.1概念介绍JavaLambda表达式是Java8中最重要的新特性之一。它们是

MySQL常见面试题(三)

😀前言在当今数据驱动的时代,数据库管理成为企业和组织的核心组件。其中,数据库的性能优化是确保信息可以快速、准确地检索的关键要素。这通常通过正确实现和管理数据库索引来实现。索引不仅可以大大提高数据库的查询性能,还可以帮助维持数据的完整性和一致性。本文将深入探讨MySQL数据库中的不同类型的索引,包括其特点和实现方式。我

《计算机视觉中的多视图几何》笔记(6)

前面的1-5章在序号上被标为Part0,标题是TheBackground:ProjectiveGeometry,TransformationsandEstimation,讲述了一些背景知识,包括投影几何、变换和估计。接下来的部分进入到Part1,标题是CameraGeometryandSingleViewGeometr

CDN内容分发系统

CDN分发系统的架构。CDN系统的缓存,也是一层一层的,能不访问后端真正的源,就不打扰它。在没有CDN的情况下,用户向浏览器输入www.web.com这个域名,客户端访问本地DNS服务器的时候,如果本地DNS服务器有缓存,则返回网站的地址;如果没有,递归查询到网站的权威DNS服务器,这个权威DNS服务器是负责web.c

热文推荐