2.2基本算法之递归和自调用函数_数据结构与算法之5——队列和栈

栈和队列比较简单,而且实用性非常广泛,这里主要介绍一下他们的概念和实现,在很多算法中,栈和队列的运用很重要,因此,虽然简单确是最重要的数据结构之一,必须重视。

栈是保证元素后进先出(后存入者先使用,Last In First Out,LIFO)的关系结构。

队列是保证元素先进先出(先存入者先使用,First In First Out,FIFO)的关系结构。

栈的顺序表实现

class  

顺序表操作的后端插入和删除操作都是o(1)操作。

栈的链接表实现:

顺序表的操作时间复杂度为o(1),那么为什么还要考虑链接表?因为顺序表扩大存储区需要做一次高代价的操作,另外顺序表需要完整的大块存储区。采用链接表实现可以缓解这两个缺陷,但是链接表也有自身的缺点:更多依赖于解释器的存储管理,每个结点的链接开销以及链接结点在实际计算机内存中的任意散布可能带来操作开销。

采用链接表技术,自然是表头一端作为栈顶,表尾作为栈底。

class  

栈和递归

这里要介绍一下栈和递归的关系,我们平时用的递归算法,其内部原理就是运用了栈。

递归:在一个函数定义中引用了被定义的对象(被定义的函数本身)。

在递归的结构中,递归的部分必须比原来的整体简单,这样才有可能达到某种终点(递归定义的出口),显然,终点不能是递归的。递归结构中必须包含非递归的基本结构构成的部分,否则就会出现无限递归。

例如阶乘函数n!,其递归的代码实现:

def 

现在来解析这一递归结构,表述其于栈之间的关系:

  • 为了得到factor(n)的结果,必须先计算出factor(n-1)。
  • 递归调用计算出factor(n-1)时还需要乘n,以得到factor(n)的结果,说明在递归调用factor(n-1)时,参数n的值需要被记录,同样计算factor(n-1)调用factor(n-2)时,也需要记录这个调用之前参数n-1的记录,向下继续...
  • 显然需要记录的数据的量与递归的层数成正比,一般而言没有数量上的上限,不能用几个整形变量来保存。
  • 在这一系列调用中保存的数据,如n,n-1..较后保存的将先被使用,因为函数返回的顺序与调用顺序相反,后进入的层次先返回。

这种后进先出的使用方式和数据项数的无明确限制,就说明需要用一个栈来支持递归函数的实际运行,这个栈称为程序运行栈

下图表示的是阶乘函数factor(3)的是如何递归计算的。

c8410ed108c74161e4cb1114f7717df5.png

45bfb69f1859231fc0dfded4b49174aa.png

第一个图表示函数调用factor(3)开始执行的状态,参数3压入栈。随后执行调用factor(2),参数2压入栈,直到factor(0)压入栈。然后开始逐一返回,其中factor(0)直接返回1(递归出口),factor(1)同样返回1*factor(0),计算得到factor值为1,factor(2)返回2factor(1)=2,factor(3)返回3*factor(2)=6。

递归的实现依赖于运行栈,对递归函数的每次调用都在栈上开辟一块区域,称为函数帧,函数的执行总以栈顶的帧作为当前帧。所有局部变量都在这里体现。当函数从下一层递归调用中返回时,函数的上一层执行取得下层函数调用得到的结果,执行系统弹出已经结束的调用对应的帧,然后回到调用前的那一层执行时的状态。

函数的嵌套调用秉持着“先调用后返回”的规则,函数调用时的内部动作分为两个部分:在进入新的函数调用前需要保存一些信息,退出一次函数调用时需要恢复调用前的状态。这被称为函数调用的前序动作和后序动作。

函数调用的前序动作包括:

  • 为被调用函数的局部变量和形式参数分配存储区(称为函数帧/活动记录/数据区)
  • 将所有实参和函数的返回地址存入函数帧(实参和形参的结合/传值)
  • 将控制转到被调用函数入口

函数调用的后序动作包括:

  • 将被调用函数的计算结果存入指定位置
  • 释放被调用函数的存储区
  • 按以前保存的返回地址将控制转回调用函数

函数的调用是有代价的,在得到程序代码的模块化和语义清晰性等优势的同时,可能会付出执行时间的代价。

递归与非递归:

将一个递归定义的函数变成非递归的形式,可以自己建立一个栈来模拟程序运行栈:

def  

任何一个递归定义的函数都可以通过引入一个栈保存中间结果的方式,翻译为一个非递归的过程。与此对应的是,任何一个包含循环的程序都可以翻译为一个不包括循环的递归函数。

栈的应用:简单背包问题(动态规划思想)

其求解算法用递归的方式描述很简单,但是通过自己管理一个栈来存储中间信息定义非递归算法,则比较复杂。

问题描述:一个背包里可放入重量为weight的物品,现有n件物品的集合S,其中物品的重量分别为w0,w1...wn-1。问题是能否从中选出若干件物品,其重量之和正好等于weight。如果存在就说这一背包有解,否则无解。

问题的求解:假设weight>=0,n>=0。用记法knap(weight,n)表示n件物品相对于总重量weight的背包问题,在考虑它是否有解时,通过考虑一件物品的选或者不选,可以把问题分为两种情况

  • 如果不选最后一件物品(wn-1),那么knap(weight,n-1)的解也就是knap(weight,n)的解,如果找到前者的解也就找到了后者的解。
  • 如果选择最后一件物品,那么knap(weight-wn-1,n-1)有解,其解加上最后一件物品就是knap(weight,n)的解,即前者有解后者也有解。

定义递归出口:

  • 重量weight已经等于0,说明问题有解
  • 重量weight已经小于0,由于不断归结中所需的重量递减,有可能出现这种情况,这说明按照已做的安排不能得到一个解。
  • 重量大于0但已经没有物品可用,说明按照这种安排无解。
def  

函数三个参数分别是总重量weight,各物体重量表wlist和物品数量n,前两个if处理三种简单情况,返回True或False(递归出口),后两种情况通过递归调用得到结果。

队列

队列的链接表实现:

带有首指针的单链表其插入操作enqueue()为o(1)操作,但是在另一端的去除操作dequeue()确实o(n)操作。那么为了实现首尾两端的插入和删除操作都为o(1)操作,则需要首尾指针都有的单链表。

class  

队列的list实现:

基于顺序表实现队列的困难:出队操作如果在首端操作,取出当时的元素后,需要将后面的元素全部前移需要o(n)时间。如果是从尾端弹出元素,时间是o(1)操作,但是尾端插入时间为o(n),因此无论何种方式进行插入和删除,都避免不了o(n)的操作。另一种可能是队首的元素弹出后,后面的元素不前移,但记住新表头元素的位置,这样做会带来另一个问题,队首的空位会越变越多,造成空间的浪费。

ecc6d136586f1b4c108aa7160d994aa6.png

有一种想法:如果入队时队尾已经达到存储区的末尾,应该考虑转到存储区开始的位置去入队新元素。

循环顺序表:

5bc55cb9c5fe69639143d113a5f7695e.png
  • 在队列使用中,顺序表的开始位置并不改变,上图是一个包含8个位置的表,例如变量q.elems始终指向表元素区开始。
  • 队头变量q.head记录当前队列里第一个元素的位置(图中位置4),队尾变量q.rear记录当前队列里最后元素之后的第一个空位(图中位置1)。
  • 队列元素保存在顺序表的一段连续单元,用python的写法是[q.head:q.rear],左闭右开区间里。图中有5个元素,从位置4到位置0的一段。两个变量的值之差(取模存储区长度)就是队列里的元素个数。

初始时队列为空,q.head和q.rear取相同值,表示顺序表里的为空,出队和入队操作时需要更新q.head和q.rear

q

如下图所示,如果队列再存一个元素,q.rear和q.head就会相同,这与判断队列为空时一样,产生冲突。事实上这种状态就看成了队列满,即定义其条件为(q.rear+1)%q.len=q.head。当队列满时又需要继续插入元素的话,就需要更换存储区更大的空间。

08877c2738bd9364cb3c5abff08ab26d.png

循环队列无法直接利用python的list里的自动存储扩充机制:队列元素的存储方式与list元素的默认存储方式不一致,list元素总是在存储区的最前面一段,而队列的元素可能是表里的任意一段,有时还分为头尾两段,如果list自动扩充,其中的队列元素就有可能失控,另一方面list没提供检查存储区容量的机制,队列操作中无法判断系统何时扩容。

考虑其基本设计:

  • 定义队列名SQueue对象里有一个list类型的成分_elems存放队列元素。
  • 分别考虑两个属性head_num表示队首元素所在位置以及表中元素的个数。
  • 用python的list作为队列存储区。需要检查当前的表是否已满,必要时换一个存储表,因此要记录当前表的长度,下面用属性_len。

数据不变式:实现一种数据结构时,最基本的问题是这些操作需要维护对象属性之间的正确关系。

  • 所有构造对象的操作,都必须把对象成分设置为满足数据不变式的状态,也就是说对象的初始状态要满足数据不变式。
  • 每个对象操作都应该保证不破坏数据不变式,也就是说如果对一个状态完好的对象应用一个操作,该操作完成时,还必须保证对象处于完好的状态。

下面队列实现中,考虑数据不变式:

  • elems属性引用着队列的元素存储区,它是一个list对象,_len属性记录存储区的有效容量。
  • _head是队列的首元素的下标,_num始终记录着队列中元素的个数。
  • 当时队列里的元素总保存在elems里从head开始的连续位置中,新入队的元素存入由_head+_num算出的位置,但如果需要把元素存入下标_len的位置时,改为在下标0位置存入该元素。
  • 在_num==_len的情况下出现入队操作,就扩大存储区。
class  

双端队列:

允许两端插入和删除元素,因此功能覆盖以上所有结构,作为效率要求很高的结构,这里要求两端插入和删除操作时间复杂度均为o(1)。双链表结构可以实现两端的常量时间插入和删除操作,可以实现双端队列。python中的collections库中定义了一种deque类型双端队列,支持元素两端的插入和删除。deque采用一种双链表技术实现,每个链接结点里顺序存储一组元素。

顺序表与链表的计算机内存分配问题:

对于现在计算机内存机制,如果连续进行的一批内存访问是局部的,操作速度就会块得多。人们在考虑效率的同时,一个重要线索就是尽可能使计算机内存的使用局部化python中顺序表是局部化的代表,在可能的情况下应该尽量使用。链接结构的一个特点是,其中的结点可能在内存中任意分配即使程序顺着链接逐个访问结点,在内存层面的表现通常也是在许多位置随机的单元之间跳来跳去因此链接表再带来灵活性的同时,效率上可能有明显的付出这些情况就是需要考虑顺序表实现的最重要原因。另外在其它的一些语言中,建立顺序表结构还可以避免复杂的存储管理。


参考书籍:《数据结构与算法—python语言描述》—裘宗燕

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

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

相关文章

神经网络与深度学习——TensorFlow2.0实战(笔记)(五)(NumPy科学计算库<2>python)

数组元素的切片 一维数组 #一维数组 #切片方法和Python序列数据结构的切片一样 anp.array([0,1,2,3,4],dtypenp.int64)#占用新的内存 #不包括结束位置 print(a[0:3]) print(a[:3]) print(a[0:]) 二维数组 #二维数组 anp.array([[0,1,2,3,4],[5,6,7,8,9],[10,11,12,13,14]],d…

c语言二进制数怎么表示_搞懂这些公式和原理,二级C语言对你来说肯定会简单很多!...

基本概念:机器数:在计算机中,一个数有二进制表示的数原码:第一位是符号位,其他位表示数值:0:正数,1:负数反码:正数-->原码,负数-->符号位不…

详细js读取execl内容并展示

作者execl内容展示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"> <!-- <script type"text/java…

python棋盘最短路径_Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例...

本文实例讲述了Python数据结构与算法之图的最短路径(Dijkstra算法)。分享给大家供大家参考&#xff0c;具体如下&#xff1a; # coding:utf-8 # Dijkstra算法——通过边实现松弛 # 指定一个点到其他各顶点的路径——单源最短路径 # 初始化图参数 G {1:{1:0, 2:1, 3:12}, 2:{2:…

js将百度坐标转为wgs84

作者execl示例 读取并转换结果如下 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- <scrip…

Jetty 服务器架构分析(中)

接上一篇&#xff0c;说到XmlConfiguration ,XmlConfiguration 利用自己实现的 IOC 组装 Server 的全过程如下图所示&#xff1a; 这里可以看到 3 个关键的配置文件&#xff0c; jetty.xml 、 jetty-deploy.xml 、以及 contexts/xxx.xml l Jetty.xml 文件中定义了…

VxWorks嵌入式操作系统的TrueFFS文件系统驱动开发

嵌入式系统对执行速度和系统可靠性的要求&#xff0c;决定了嵌入式系统需要一种安全、快速的存储设备&#xff0c;这种设备备同时还需要体积小、容量大、掉电数据不丢失等特点。而Flash存储器恰恰能够满足上述要求。这也使得Flash存储器成为嵌入式系统中的主要存储设备。 现…

神经网络与深度学习——TensorFlow2.0实战(笔记)(五)(NumPy科学计算库<矩阵和随机数>python)

矩阵和随机数 矩阵 创建矩阵 #创建矩阵 astring np.mat("1 2 3; 4 5 6") alist [[1,2,3],[4,5,6]] anplist np.array(alist) print(np.matrix(astring))#字符串、列表、元组、数组 print(np.mat(astring))#字符串、列表、元组、数组 print(np.mat(alist)) prin…

神经网络与深度学习——TensorFlow2.0实战(笔记)(五)(Matplotlib绘图基础<1>python)

数据可视化 数据分析阶段&#xff1a;理解和洞察数据之间的关系 算法调试阶段&#xff1a;发现问题&#xff0c;优化算法 项目总结阶段&#xff1a;展示项目成果 Matplotlib&#xff1a; 第三方库&#xff0c;可以快速方便地生成高质量的图表 安装Matplotlib库 Figure 对…

idea lombok不生效_Spring Boot 集成 Lombok 让代码更简洁!

点击上方“Java之间”&#xff0c;选择“置顶或者星标”你关注的就是我关心的&#xff01;作者&#xff1a;Anoyi lombok的威力简化代码IntelliJ IDEA安装lombok插件1、菜单栏 File > Settings > Plugins > Browse repositories…安装插件2、搜索 Lombok Plugin 安装后…

arcgis按属性设置符号大小

一般都在高级设置里&#xff0c;以下是两个示例 1.相同颜色&#xff0c;不同大小 2不同颜色&#xff0c;不同大小

druiddatasource配置_Spring核心配置文件详解

点击蓝字“程序员考拉”欢迎关注&#xff01;1&#xff1a;spring的核心配置文件中的各种配置。 spring的核心配置文件的名字 叫做 applicationContext.xml&#xff0c;后期也可以通过配置文件中的配置修改名称&#xff0c;在web.xml中进行如下配置&#xff1a;<contex…

origin(1)

origin窗口结构与布局 工作表 0.数据导入 0.1直接拖拽 0.2导入 配置相关参数 1.添加新列 1.1方法一 1.2方法二 2.设置x&#xff0c;y&#xff0c;z 2.1设置单列 2.2设置多列 2.3设置无关列 2.4设置误差线 2.5设置为标签 3.长短名称设置 3.1长名称设置 3.1.1方法一 3.1.2方…

神经网络与深度学习——TensorFlow2.0实战(笔记)(五)(Matplotlib绘图基础<散点图>python)

散点图&#xff08;Scatter&#xff09;&#xff1a; 是数据点在直角坐标系中的分布图 scatter() 函数 marker参数——数据点样式 添加文字——text() 函数 坐标轴设置 增加图例 绘制标准正态分布的散点图步骤 #散点图&#xff08;Scatter&#xff09;&#xff1a;是数据点在直…

十字路口红绿灯plc程序_实例讲解红绿灯PLC程序设计方法

十字路口的交通指挥信号灯布置如下图&#xff1a;一、控制要求&#xff08;1&#xff09;信号灯系统由一个启动开关控制&#xff0c;当启动开关接通时&#xff0c;该信号灯系 统开始工作&#xff0c;当启动开关关断时&#xff0c;所有信号灯都熄灭。&#xff08;2&#xff09;南…

listview刷新_Flutter NestedScrollView 滑动折叠头部下拉刷新效果

题记—— 执剑天涯&#xff0c;从你的点滴积累开始&#xff0c;所及之处&#xff0c;必精益求精。Flutter是谷歌推出的最新的移动开发框架。本实例运行效果如下 &#xff1a;//启动函数void main() { runApp(RootApp());}//根目录class RootApp extends StatelessWidget { ov…

神经网络与深度学习——TensorFlow2.0实战(笔记)(六)(Matplotlib绘图基础<折线图和柱状图>python)

折线图&#xff08;Line Chart&#xff09;&#xff1a; 散点图的基础上&#xff0c;将相邻的点用线段相连接 plot()函数 #折线图&#xff1a;在散点图的基础上将相邻两个点链接 #描述变量变化的趋势 #plot(x,y,color,marker,label,linewidth,markersize) #x数据点的x坐标 #y…

WinCE6.0的EBOOT概要

为一个新的硬件设备定制WinCE6.0操作系统&#xff0c;一般需要完成以下几个主要步骤&#xff1a; 1. 针对特定的硬件设备创建板级支持包(Board Support Package缩写为BSP)&#xff0c;BSP必须包括BOOTLOADER、OEM适配层(OEM Adaptation Layer缩写为OAL)和一些必要的驱动。…

Silverlight HTML5 Flash - RIA技术之三足鼎立

未来&#xff0c;“用户体验”将成为所有软件商业价值的首要衡量标准。拥有极好用户体验的RIA(富互联网应用)技术近些年来发展迅猛&#xff0c;其中以Silverlight、HTML5及Flash最受热捧。纵观&#xff0c;互联网上98%的计算机都有安装Flash&#xff1b;HTML5的新特性则强化了W…

python i开发工具_Python轻量级开发工具Genay使用

Genay是一个轻量级的免费&#xff0c;开放源代码的开发工具&#xff0c;支持很多的文件类型&#xff0c;并且支持很多的插件&#xff0c;启动快速。安装包只有十几兆&#xff0c;相比pycharm专业版需要收费&#xff0c;并且社区版的安装包大小有两百多兆&#xff0c;对于python…