算法day12

算法day12

  • 二叉树理论基础
  • 114 二叉树的前序遍历
  • 145 二叉树的后序遍历
  • 94 二叉树的中序遍历
  • 迭代法

二叉树理论基础

直接看代码随想录就完事了,之前考研也学过,大概都能理解
我这里就说说代码层面的。
二叉树的存储:
1、链式存储:这个就是我们平时用的左指针,右指针那种写法的二叉树存储方式。
2、顺序存储:这个就是利用数组来存二叉树,值得一提的是,结点与结点的孩子如何表示,这个是通过下标直接来表示的,如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

二叉树遍历
深度优先遍历
前序遍历(递归法,迭代法)
中序遍历(递归法,迭代法)
后序遍历(递归法,迭代法)
广度优先遍历
层次遍历(迭代法)
一个技巧:前中后这里指的是访问根节点的顺序。
前中后遍历的逻辑其实都可以借助栈使用递归的方式来实现。

二叉树的定义

type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode
}

这简直和C++一个样。


二叉树的递归遍历

下面的题目都是涉及二叉树的递归遍历。递归的流程我觉得是很简单的,但是这个代码有时候容易搞混。所以这里对递归的写法做一个总结。

每次写递归都按照这三要素来写
1.确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型
2.确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
3.确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。


114二叉树的前序遍历

我知道逻辑就是先根后左右。c++能写但是go写不出来,看到go语言的代码我就傻掉了。
根本写不出
所以这里好好学学go语言怎么写的。
首先树结点的结构体这个必须要会写,就和c++的实现一样的。

func preorderTraversal(root *TreeNode) (res []int) {var traversal func(node *TreeNode)traversal = func(node *TreeNode) {if node == nil {return}res = append(res,node.Val)traversal(node.Left)traversal(node.Right)}traversal(root)return res
}

代码逻辑:
1.在这个函数中,先定义一个递归函数traversal ,注意这个函数是直接在内部定义的,这个语法我说实话我还是学的太少了,第一次见。这里把这个语法学了。
函数是可以在另一个函数的内部定义的,定义的方式如下:

traversal = func(node *TreeNode) {if node == nil {return}res = append(res,node.Val)traversal(node.Left)traversal(node.Right)}

我总结就是先声明然后进行实现,这个语法格式了解之后多写写就有记忆了。

这个题我只能说已经结束了,这其实就是利用了go语言的一个性质和leetcode题意要求,由于leetcode要求返回的是结果切片,所以这里我就要在这个函数里面调用一个函数,从而直接返回我要的结果。所以说这个题也并非要按题解这么写。这个函数我可以放到外面定义的,只是go语言有这个方便的性质。

这里我再写一个版本

func traversal(root *TreeNode,res *[]int){if root == nil {return }*res = append(*res,root.Val)traversal(root.Left,res)traversal(root.Right,res)
}func preorderTraversal(root *TreeNode) []int {res := []int{}traversal(root,&res)return res
}

这个我感觉更适合我理解的写法。
但是这个写法我自己写的时候也遇到问题:
1.我第一次写的时候没有意思到,res切片是引用类型,所以我在函数调用的时候,如果要对res产生影响,那么我就必须要传指针,所以在定义函数的时候我要传的是*[]int,然后传参的时候传的肯定是&res。
2.还有一个同样的问题发生在append,我在对res调用append的时候,这个res必须是引用类型,所以我就要加一个取值符号*,对里面的参数一样如此。

总结:注意引用类型。

自己写了这个版本之后我才知道题解这么用有啥好处,题解这么写还用到了一个语法:闭包

这里通过这个题再学学闭包:
闭包是一种特殊的函数,它可以捕获其创建时所在的作用域中的变量。在go语言中,闭包是通过定义在另一个函数内部的匿名函数实现的。这些匿名函数能够访问并操作其外部函数的变量。

我相信看完这个闭包的定义,肯定有很大的疑惑,我这个代码里面不是有名字吗?这里其实很多教程又省略了,我又去查了资料。

这里我建议看我理解的这个:
上面代码中,严格来说我实现闭包的这个函数并不是匿名函数,然而,即使它被命名了,它仍然可以访问并修改它的外部作用域(PreorferTraversal函数)中的变量res。它仍然符合闭包的行为特征。

所以这里就要进行更正:
闭包的本质
闭包的核心特征是它能够捕获并使用其外部作用域中的变量。**在go语言中,这通常是通过在一个函数内部定义另一个函数实现的。**这个内部函数可以是匿名的,也可以是命名的。关键是前面加粗的这句话。

题解的做法完全就是利用了闭包的性质。


后面两个题很简单

145二叉树的后续遍历

func postorderTraversal(root *TreeNode) []int {res := []int{}var traversal func(root *TreeNode)traversal = func(root *TreeNode){if root == nil {return }traversal(root.Left)traversal(root.Right)res = append(res,root.Val)}traversal(root)return res
}

就是左右根


94 二叉树的中序遍历

左根右

func inorderTraversal(root *TreeNode) []int {res := []int{}var traversal func(root *TreeNode)traversal = func(root *TreeNode){if root == nil {return }traversal(root.Left)res = append(res,root.Val)traversal(root.Right)}traversal(root)return res
}

迭代法(非递归实现遍历)

思路:非递归的实现其实就是用栈来模拟递归,因为递归的实现就是每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
所以非递归的代码实现就是用栈来实现。所以下面的代码我都会先开一个栈来辅助。

前序遍历

思路:前序遍历是中左右,每次先处理中间结点,那么就先将根结点放入栈中,然后将右孩子加入栈,再加入左孩子(有人可能觉得这里我搞返了)。
为什么要先加入右孩子再加入左孩子?因为这样出栈的时候才是中左右的顺序。
可以看看这个动画模拟实现的过程,看看过程中遍历到不同结点时,栈的变化
请添加图片描述
我对这个过程解读以下:
一开始5先入栈,5出栈,5的右孩子(6)入栈,然后左孩子(4)入栈。
此时栈的状态的结果 前序遍历输出5 栈中 6 4.
4出栈,然后4的右孩子2入栈,左孩子1入栈,此时 前序遍历输出 5 4 ,栈中6 2 1
1 出栈,由于1没有左右孩子,那继续2出栈,2也没有左右孩子,然后6出栈,6也没有左右孩子,所以这个时候栈为空,结束。

代码逻辑:
1.既然要用到栈,那就先把栈实现了,这里要注意这栈的元素是什么,是值?还是树结点?回答是树结点,这里需要注意。在上面的图中可能显得稍微简化了一点,但实际上是把树结点加入栈中。就像我们写递归写法一样,我们进行递归下一层同样是对结点操作。
2.实现前序遍历的非递归解法:
1.先创建一个刚刚实现的空栈,再创建一个结果集装结果。
2.先将根结点root先入栈,然后开始循环
3.for 循环是针对这个栈的,只要栈不空就不会停止。
将栈顶元素出栈,然后把这个栈顶元素的值加入结果集。然后先加入右结点,再加入左结点

代码实现

type Stack []*TreeNodefunc (s *Stack) Push(node *TreeNode) {*s = append(*s, node)
}func (s *Stack) Pop() *TreeNode {if s.IsEmpty() {return nil}index := len(*s) - 1element := (*s)[index]*s = (*s)[:index]return element
}func (s *Stack) IsEmpty() bool {return len(*s) == 0
}func preorderTraversal(root *TreeNode) []int {if root == nil {return nil}stack := Stack{}stack.Push(root)result := []int{}for !stack.IsEmpty() {node := stack.Pop()result = append(result, node.Val)if node.Right != nil {stack.Push(node.Right)}if node.Left != nil {stack.Push(node.Left)}}return result
}

中序遍历迭代法

注意有人可能想像递归写法一样,我在前序遍历非递归的写法,改以下就得到中序遍历迭代法。这是实现不了的,因为不是一个逻辑。前序遍历的逻辑无法直接用到中序遍历上。
为什么中序写法和前序的写法不通用?
因为前序的遍历顺序是根左右,先访问的元素是中间结点,而且要处理的元素也是中间结点,所以才能写出相对简洁的代吗,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
再看看中序:中序是左根右,先访问的是二叉树顶部的结点然后一层一层的往下层访问,直到到达树的最左下,然后才开始处理结点(也就是访问结点),这就造成了处理顺序和访问顺序是不一致的。

那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

思路:
中序遍历的顺序是左中右
1.初始化栈和当前结点:创建一个存待访问的结点,然后设置当前结点为根结点。

看懂这个就看懂了这个题的逻辑:
什么是当前结点当前结点是正在处理的元素,为什么需要当前结点,在非递归的实现中,需要一个指针或引用来追踪当前正在处理的结点,这个指针就被称为当前结点。
当前结点如何用? 遍历时不断地讲当前结点移动到其左子节点,直到到达最左侧的结点。这个过程中,沿途的结点都被推入栈中,以备后续访问。
当到达最左侧结点且该结点没有左子结点时,开始从栈中弹出结点,每次从栈中弹出一个结点,这个结点就称为新的”当前结点”,然后处理这个结点,并将当前结点移动到其右子结点。

2.遍历左子树:当前节点非空时,将其推入栈中,并将当前节点设置为其左子节点。这个过程一直持续到最左端,即没有左子节点为止。

3.访问节点和遍历右子树:当前节点为空时(表示到达最左端),从栈中弹出一个元素(这是当前最左侧的节点),访问这个节点(将节点值加入结果数组),将“当前节点”设置为弹出节点的右子节点(开始处理右子树)

4.重复过程:重复上述过程,直到栈为空且当前节点也为空。这表示所有节点都已按中序遍历的顺序访问完毕。

核心:
1.栈的使用: 栈用来暂存还未访问的节点。由于栈的后进先出特性,它能够保证在处理完左子树后能够回到相应的父节点,再去处理右子树。

2.遍历到最左侧: 一开始,需要遍历到树的最左侧,这是中序遍历开始访问节点的地方。

3.从栈中弹出节点: 在没有左子节点可访问时,从栈中弹出节点,表示该节点的左子树已经处理完毕,可以访问该节点了。

4.转向右子树: 访问完节点后,转向处理右子树,即将当前节点设置为右子节点。

代码:

func inorderTraversal(root *TreeNode) []int {var result []intstack := []*TreeNode{}currentNode := rootfor currentNode != nil || len(stack) > 0 {// 遍历到最左节点for currentNode != nil {stack = append(stack, currentNode)currentNode = currentNode.Left}// 当前节点为空,说明左边遍历到底了,开始处理栈顶节点currentNode = stack[len(stack)-1]stack = stack[:len(stack)-1]  // 弹出栈顶节点result = append(result, currentNode.Val)  // 添加到结果中// 转向右子树currentNode = currentNode.Right}return result
}

后序遍历非递归实现:

后序遍历只需要在前序遍历的基础是做调整就可以实现,为什么?
先序遍历是根左右,后续遍历是左右根,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了。

可能这样看着还是有点不太懂。先看代码逻辑,我来解读:


func postorderTraversal(root *TreeNode) []int {if root == nil {return nil}stack := []*TreeNode{root} // 创建一个栈并初始化为包含根节点result := []int{} // 结果数组,用于存储遍历结果// 继续遍历直到栈为空for len(stack) > 0 {node := stack[len(stack)-1] // 取出栈顶元素stack = stack[:len(stack)-1] // 弹出栈顶元素// 将当前节点的值添加到结果数组的前面// 这样做是为了在最后反转遍历的顺序result = append([]int{node.Val}, result...)// 如果存在左子节点,将其推入栈中// 注意这里先处理左子节点,因为我们希望它在右子节点之后被处理if node.Left != nil {stack = append(stack, node.Left)}// 如果存在右子节点,将其推入栈中if node.Right != nil {stack = append(stack, node.Right)}}// 返回结果数组,这个数组是“左-右-根”的顺序return result
}

1.栈还是和前序一样的用法,用于存储待访问的解读,首先将根节点压入栈中。
2.根-右-左的遍历顺序,我们从栈中弹出一个节点,首先访问这个节点(根),然后,如果存在左子节点,我们将其压入栈中,接下来,如果存在右子节点,我们也将其压入栈中。由于栈是后进先出的,这意味着右子节点将在左子节点之前被处理。
3.结果数组的构建:每次从栈中弹出一个节点时,我们将其值插入到结果数组的前端,这样做实际上是在创建一个“根-右-左”的顺序的数组。但是,因为我们总是在数组的前端插入新元素,所以最终的数组实际上是“左-右-根”的顺序。

为什么这样做可以得到后序遍历的结果?
在后序遍历中,我们希望遵循“左-右-根”的顺序。由于我们是在结果数组的前端插入每个新访问的节点,所以最后访问的节点(根节点)将出现在数组的最前面,而第一个访问的节点(最左侧的节点)将出现在数组的最后面。这样,即使我们的遍历顺序是“根-右-左”,数组中元素的顺序却是“左-右-根”。

递归写法总结:算法实现还是非常的好理解的
主要还是对go语言的语法学习。今天这个语法我也是头一次体会到了闭包的作用。当时学语法的时候由于没有使用场景,所以学起来还是有很大差别的。

应用场景总结:
在函数调用的时候,对于一个引用类型,如果你想通过函数调用对这个引用类型产生影响,这个时候要么传指针,要么用闭包。而且对于有些函数调用,比如append,我还需要对传递的指针通过取值符号*进行取值后才能调用这个函数。

递归写法和非递归写法哪个比较好?好在哪里?
递归写法:
优点:代码好写,对于要解决的问题来说,这种写法与问题直接的关系就显得很直观。
缺点:递归可能会导致调用堆栈过深,尤其是处理大规模数据的时候,可能会出现堆栈溢出。而且每次递归调用都会增加额外的堆栈帧开销,影响性能。

非递归写法:
优点:通常比递归写法有更好的性能表现,而且无堆栈溢出风险。
缺点:代码不好写,代码的可读性下降,设计比较困难。

总结:递归好写性能差,非递归难写性能好。

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

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

相关文章

基础面试题整理6之Redis

1.Redis的应用场景 Redis支持类型:String、hash、set、zset、list String类型 hash类型 set类型 zset类型 list类型 一般用作缓存,例如 如何同时操作同一功能 2.redis是单线程 Redis服务端(数据操作)是单线程,所以Redis是并发安全的,因…

Mysql MGR搭建

一、架构说明 1.1 架构概述 MGR(单主)VIP架构是一种分布式数据库架构,其中数据库系统采用单主复制模式, 同时引入虚拟IP(VIP)来提高可用性和可扩展性。 这种架构结合了传统主从复制和虚拟IP技术的优势,为数据库系统提供了高可用、 高性能和…

十分钟GIS——geoserver+postgis+udig从零开始发布地图服务

1数据库部署 1.1PostgreSql安装 下载到安装文件后(postgresql-9.2.19-1-windows-x64.exe),双击安装。 指定安装目录,如下图所示 指定数据库文件存放目录位置,如下图所示 指定数据库访问管理员密码,如下图所…

uniapp canvas游标卡尺效果

效果 根据公司业务仿照写的效果。原项目从微信小程序转uniapp,未测试该效果在android端效果。 uniapp直接使用canvas不可做子组件,否则无效果显示,其次显示时要考虑页面渲染超时的问题。 如效果所见,可以设置取值精度。 gitee地址:project_practice: 项目练习 - Gitee.…

用python编写爬虫,爬取房产信息

题目 报告要求 工程报告链接放在这里 https://download.csdn.net/download/Samature/88816284使用 1.安装jupyter notebook 2.用jupyter notebook打开工程里的ipynb文件,再run all就行 注意事项 可能遇到的bug 暂无,有的话私信我

Java 中使用多线程的方式有哪些

在 Java 中,使用多线程主要有以下几种方式: 实现 Runnable 接口:这是实现多线程的一种方式。一个类实现 Runnable 接口后,就意味着它是一个线程的执行对象。当一个线程启动时,需要一个与之关联的 Runnable 对象。 publ…

Netflix Mac(奈飞mac客户端) v2.13.0激活版

Clicker for Netflix Mac版是一款适用于Mac的最佳独立Netflix播放器,具有直接从从Dock启动Netflix,从触摸栏控制Netflix,支持画中画等多种功能,让你拥有更好的观看体验。 软件特色 •直接从Dock启动Netflix •从触摸栏控制Netflix…

每日一练:LeeCode-112、路径总和【二叉树+DFS+回溯】

本文是力扣LeeCode-112、路径总和 学习与理解过程,本文仅做学习之用,对本题感兴趣的小伙伴可以出门左拐LeeCode。 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有…

HarmonyOS4.0——IPC与RPC通信

基本概念 IPC(Inter-Process Communication)与RPC(Remote Procedure Call)用于实现跨进程通信,不同的是前者使用Binder驱动,用于设备内的跨进程通信,后者使用软总线驱动,用于跨设备…

【iOS ARKit】人形遮挡

人形遮挡简介 在 AR系统中,计算机通过对设备摄像头采集的图像进行视觉处理和组织,建立起实景空间,然后将生成的虚拟对象依据几何一致性原理嵌入到实景空间中,形成虚实融合的增强现实环境,再输出到显示系统中呈现给使用…

【数据结构】链表OJ面试题3(题库+解析)

1.前言 前五题在这http://t.csdnimg.cn/UeggB 后三题在这http://t.csdnimg.cn/gbohQ 记录每天的刷题,继续坚持! 2.OJ题目训练 9. 给定一个链表,判断链表中是否有环。 力扣(LeetCode)官网 - 全球极客挚爱的技术成…

假期最好的安排:读书学习成长

假期是每个人放松身心、充电学习的好时机。然而,很多人往往会将假期用于休闲娱乐,错失了充实自己的宝贵机会。本文将介绍如何通过读书学习成长,让你度过一个充实、有意义的假期。 一、知识拓展 假期是知识拓展的好时机。你可以选择一些与你…

嵌入式中《C++之旅》阅读笔记

constexpr constexpr的隐含意思是在编译阶段求值,对于一些求值操作,如果声明为constexpr,那么会编译器会尝试在编译阶段进行计算求值,如果求值成功,则用结果进行替换。 一个常用的例子是如下: constexpr…

深入解析 Spring 事务机制

当构建复杂的企业级应用程序时,数据一致性和可靠性是至关重要的。Spring 框架提供了强大而灵活的事务管理机制,成为开发者处理事务的首选工具。本文将深入探讨 Spring 事务的使用和原理,为大家提供全面的了解和实际应用的指导。 本文概览 首…

ORM模型类

模型 创建两个表 创建模型类 from django.db import models# Create your models here. class BookInfo(models.Model):name models.CharField(max_length10, uniqueTrue) # 书名pub_date models.DateField(nullTrue) # 发布时间read_count models.IntegerField(default…

【JSON2WEB】04 amis低代码前端框架介绍

1 什么是 amis amis 是一个低代码前端框架,它使用 JSON 配置来生成页面,可以减少页面开发工作量,极大提升效率。 看到amis一句话的介绍,感觉就是JSON2WEB要找的前端框架。 amis是百度开源的框架,毕竟是大厂&#xff0c…

即插即用、简单有效的大语言模型推荐算法!港大联合百度推出RLMRec

论文链接: https://arxiv.org/abs/2310.15950 论文代码: https://github.com/HKUDS/RLMRec 实验室主页: https://sites.google.com/view/chaoh/group-join-us?authuser0 TLDR 本文从互信息最大化的理论角度出发,通过引入文本信号…

使用No-SQL数据库支持连接查询用例的讨论

简介 在本文中,我们将简单介绍什么是No-SQL数据库。然后我们会讨论一种使用关系数据库比较容易实现的查询,即连接查询,怎么样使用No-SQL来实现。 什么是No-SQL数据库 与No-SQL数据库相对应的是传统的关系数据库(RDBMS&#xff…

JRT监听程序

本次设计避免以往设计缺陷,老的主要为了保持兼容性,在用的设计就不好调了。 首先,接口抽象时候就不在给参数放仪器ID和处理类了,直接放仪器配置实体,接口实现想用什么属性就用什么属性,避免老方式要扩参数时…

java的excel列行合并模版

1.效果 2.模版 <tableborder"1"cellpadding"0"cellspacing"0"class"tablebor"id"TABLE"><tr align"center" class"bg217"><td style"background-color: #008000; color: #ffffff;p…