数据结构 | 递归

目录

一、何谓递归

1.1 计算一列数之和

1.2 递归三原则

1.3 将整数转换成任意进制的字符串

二、栈帧:实现递归

三、递归可视化

四、谢尔平斯基三角形

五、复杂的递归问题

六、动态规划


一、何谓递归

递归是解决问题的一种办法,它将问题不断地分成更小的子问题,直到子问题可以用普通的方法解决。

1.1 计算一列数之和

假设需要计算数字列表[1,3,5,7,9]的和。

循环求和函数如下:

def listsum(numList):
    theSum=0
    for i in numList:
        theSum=theSum+i
    return theSum

递归求和函数如下:(递归调用)

def listsum(numList):
    if len(numList)==1:
        return numList[0]
    else:
        return numList[0]+listsum(numList[1:])

1.2 递归三原则

所有的递归算法都要遵守的三个重要原则如下:
(1)递归算法必须有基本情况

(2)递归算法必须改变其状态并向基本情况靠近;

(3)递归算法必须递归地调用自己。

基本情况是指使算法停止递归的条件,这通常是小到足以直接解决的问题。

为了遵守第二条原则,必须设法改变算法的状态,从而使其向基本情况靠近。改变状态是指修改算法所用的某些数据,这通常意味着代表问题的数据以某种方式变得更小。

最后一条原则是递归算法必须对自身进行调用。

1.3 将整数转换成任意进制的字符串

利用递归将整数转换成以2~16为进制基数的字符串的代码如下:

def toStr(n,base):
    converString="0123456789ABCDEF"
    if n<base:
        return converString[n]
    else:
        return toStr(n//base,base)+converString[n%base]

二、栈帧:实现递归

假设不拼接递归调用toStr的结果和converString的查找结果,而是在进行递归调用之前把字符串压入栈中。

rStack=Stack()

def toStr(n,base):
    converString="0123456789ABCDEF"
    if n<base:
        rStack.push(converString[n])
    else:
        rStack.push(converString[n%base])
        toStr(n//base,base)

当调用函数时,Python分配一个栈帧来处理该函数的局部变量。当函数返回时,返回值就在栈的顶端,以供调用者访问。

栈帧限定了函数所用变量的作用域。尽管反复调用相同的函数,但是每一次调用都会为函数的局部变量创建新的作用域。

三、递归可视化

我们将使用Python的turtle模块来绘制图案。

使用turtle模块绘制螺旋线的代码如下。先导入turtle模块,然后创建一个小乌龟对象,同时也会创建用于绘制图案的窗口。接下来定义drawSpiral函数。这个简单函数的基本情况是,要画的线的长度(参数len)降为0。如果线的长度大于0,就让小乌龟向前移动len个单位距离,然后向右转90度。递归发生在用缩短后的距离再一次调用drawSpiral函数时。在结尾处调用了myWin.exitonclick()函数,这使小乌龟进入等待模式,直到用户在窗口内再次点击之后,程序清理并退出。

from turtle import *

myTurtle=Turtle()
myWin=myTurtle.getscreen()

def drawSpiral(myTurtle,lineLen):
    if lineLen>0:
        myTurtle.forward(lineLen)
        myTurtle.right(90)
        drawSpiral(myTurtle,lineLen-5)

drawSpiral(myTurtle,100)
myWin.exitonclick()

接下来绘制一颗分形树。分形是数学的一个分支,它与递归有很多共同点。分形的定义是,不论放大多少倍来观察分形图,它总是有相同的基本形状。

 绘制分形图的代码如下:

def tree(branchLen,t):
    if branchLen>5:
        t.forward(branchLen)
        t.right(20)
        tree(branchLen-15,t)
        t.left(40)
        tree(branchLen-10,t)
        t.right(20)
        t.backward(branchLen)

from turtle import *
t=Turtle()
myWin=t.getscreen()
t.left(90)
t.up()
t.backward(300)
t.down()
t.color('green')
tree(110,t)
myWin.exitonclick()

四、谢尔平斯基三角形

from turtle import *

def drawTriangle(points,color,myTurtle):
    myTurtle.fillcolor(color)
    myTurtle.up()
    myTurtle.goto(points[0])
    myTurtle.down()
    myTurtle.begin_fill()
    myTurtle.goto(points[1])
    myTurtle.goto(points[2])
    myTurtle.goto(points[0])
    myTurtle.end_fill()

def getMid(p1,p2):
    return ((p1[0]+p2[0])/2,(p1[1]+p2[1])/2)

def sierpinski(points,degree,myTurtle):
    colormap=['blue','red','green','white','yellow','violet','orange']
    drawTriangle(points,colormap[degree],myTurtle)
    if degree>0:
        sierpinski([points[0],getMid(points[0],points[1]),getMid(points[0],points[2])],degree-1,myTurtle)
        sierpinski([points[1], getMid(points[0], points[1]), getMid(points[1], points[2])], degree - 1, myTurtle)
        sierpinski([points[2], getMid(points[2], points[1]), getMid(points[0], points[2])], degree - 1, myTurtle)

myTurtle=Turtle()
myWin=myTurtle.getscreen()
myPoints=[(-500,250),(0,500),(500,-250)]
sierpinski(myPoints,5,myTurtle)
myWin.exitonclick()

五、复杂的递归问题

汉诺塔问题

假设一共有三根柱子,第一根柱子起初有5个盘子,任务是将5个盘子从一根柱子移动到另一根柱子,同时有两个重要的限制条件:每次只能移动一个盘子,并且大盘子不能放在小盘子之上。

如果我们知道如何把上面4个盘子移动到第二根柱子上,那么久轻易地把最底下的盘子移动到第三根柱子上,然后将4个盘子从第二根柱子移动到第三根柱子。但是如果不知道如何移动4个盘子,该怎么办呢?如果我们知道如何把上面的3个盘子移动到第三根柱子上,那么就轻易地把第4个盘子移动到第二根柱子上,然后再把3个盘子从第三个柱子上移动到第二根柱子上。但是如果不知道如何移动3个盘子,该怎么办呢?移动两个盘子到第二根柱子上,然后把第3个盘子移动到第三个柱子上,最后把两个盘子移过来,怎么样?但是如果还是不知道如何移动两个盘子,该怎么办呢?把一个盘子移动到第三个柱子并不难,甚至太简单了。这看上去就是这个问题的基本情况。

以下概述如何借助一根中间柱子,将高度为height的一叠盘子从起点柱子移到终点柱子:

(1)借助终点柱子,将高度为height-1的一叠盘子移到中间柱子;

(2)将最后一个盘子移到终点柱子;

(3)借助起点柱子,将高度为height-1的一叠盘子从中间柱子移到终点柱子。

def moveTower(height,fromPole,toPole,withPole):
    if height>=1:
        moveTower(height-1,fromPole,withPole,toPole)
        moveDisk(fromPole,toPole)
        moveTower(height-1,withPole,toPole,fromPole)

def moveDisk(fp,tp):
    print("moving disk from %d to %d\n"%(fp,tp))

汉诺塔问题的详细讲解(python版)https://blog.csdn.net/wistonty11/article/details/123563309

六、动态规划

在解决优化问题时,一个策略是动态规划。

优化问题的一个经典例子就是在找零时使用最少的硬币。假设某个自动售货机制造商希望在每笔交易中给出最少的硬币。一个顾客使用一张一美元的纸币购买了价值37美元的物品,最少需要找给该顾客多少硬币呢?答案是6枚:25美分的2枚,10美分的1枚,1美分的3枚。该如何计算呢?从面值最大的硬币(25美分)开始,使用尽可能多的硬币,然后尽可能地使用面值第2大的硬币。这种方法叫做贪婪算法——试图最大程度地解决问题。

算法基础:贪婪算法(基于Python)https://blog.csdn.net/leowinbow/article/details/88140107让我们来考察一种必定能得到最优解的方法。这是一种递归方法。首先确定基本情况:如果要找的零钱金额与硬币面值相同,那么只需找1枚硬币即可。

如果要找的零钱硬币和硬币的面值不同,则有多种选择:1枚1分的硬币加上找零钱金额减去1分之后所需的硬币;1枚5分的硬币加上找零金额减去5分之后所需的硬币;1枚10分的硬币加上找零金额减去10分之后所需的硬币;1枚25分的硬币加上找零金额减去25分之后所需的硬币。我们需要从中找到硬币数最少的情况,如下所示:

numCoins=min(1+numCoins(originalamount-1),1+numCoins(originalamount-5),1+numCoins(originalamount-10),1+numCoins(originalamount-20))

找零问题的递归解决方案如下:

def recMC(coinValueList,change):
    minCoins=change
    if change in coinValueList:
        return 1
    else:
        for i in [c for c in coinValueList if c<=change]:
            numCoins=1+recMC(coinValueList,change-i)
            if numCoins<minCoins:
                minCoins=numCoins
    return minCoins

recMC([1,5,10,25],63)

显然,该算法将大量时间和资源浪费在了重复计算已有的结果上。

减少计算量的关键在于记住已有的结果。简单的做法是把最少硬币数的计算结果存储在一张表中,并在计算新的最少硬币数之前,检查结果是否已在表中。如果是,就直接使用结果,而不是重新计算。

添加查询表之后的找零算法:

def recDC(coinValueList,change,knownResults):
    minCoins=change
    if change in coinValueList:
        knownResults[change]=1
        return 1
    elif knownResults[change]>0:
        return knownResults[change]
    else:
        for i in [c for c in coinValueList if c<=change]:
            numCoins=1+recDC(coinValueList,change-i,knownResults)
            if numCoins<minCoins:
                minCoins=numCoins
                knownResults[change]=minCoins
    return minCoins

recDC([1,5,10,25],63,[0]*64)

上面所做的优化并不是动态规划,而是通过记忆化(或者叫作缓存)的方法来优化程序的性能。

真正的动态规划算法会用更系统化的方法来解决问题。在解决找零问题时,动态规划算法会从1分找零开始,然后系统地一直计算到所需的找零金额。这样做可以保证在每一步都已经知道任何小于当前值的找零金额所需的最少硬币数。

在这个过程中,从1分开始,只需找1枚1分的硬币。第2行展示了1分和2分所需的最少硬币数。同理,2分只需找2枚1分的硬币。第5行开始变得有趣起来,此时我们有2个可选方案:要么找5枚1分的硬币,要么找1枚5分的硬币。哪个方案更好呢?查表后发现,4分所需的最少硬币数是4,再加上1枚1分的硬币就得到5分(共需要5枚硬币);如果直接找1枚5分的硬币,则最少硬币数是1.由于1比5小,因此我们把1存入表中。接着来看11分的情况,我们有3个可选方案:

(1)1枚1分的硬币加上找10分零钱(11-1)最少需要的硬币(1枚)。

(2)1枚5分的硬币加上找6分零钱(11-5)最少需要的硬币(2枚)。

(3)1枚10分的硬币加上找1分零钱(11-10)最少需要的硬币(1枚)。

第1个和第3个方案均可得到最优解,即共需要2枚硬币。

找零问题的动态规划解法代码如下所示。dpMakeChange接受3个参数:硬币面值列表、找零金额,以及由每一个找零金额所需的最少硬币数构成的列表。当函数运行结束时,minCoins将包含找零金额从0到change的所有最优解。

def dpMakeChange(coinValueList,change,minCoins):
    for cents in range(change+1):
        coinCount=cents
        for j in [c for c in coinValueList if c<=cents]:
            if minCoins[cents-j]+1<coinCount:
                coinCount=minCoins[cents-j]+1
        minCoins[cents]=coinCount
    return minCoins[change]

dpMakeChange并不是递归函数。

修改后的动态规划解法:
 

def dpMakeChange(coinValueList,change,minCoins,coinsUsed):
    for cents in range(change+1):
        coinCount=cents
        newCoin=1
        for j in [c for c in coinValueList if c<=cents]:
            if minCoins[cents-j]+1<coinCount:
                coinCount=minCoins[cents-j]+1
                newCoin=j
        minCoins[cents]=coinCount
        coinsUsed[cents]=newCoin
    return minCoins[change]

def printCoins(coinsUsed,change):
    coin=change
    while coin>0:
        thisCoin=coinsUsed[coin]
        print(thisCoin)
        coin=coin-thisCoin

cl=[1,5,10,21,25]
coinsUsed=[0]*64
coinCount=[0]*64
dpMakeChange(cl,63,coinCount,coinsUsed)
print(printCoins(coinsUsed,63))
print(printCoins(coinsUsed,52))
print(coinsUsed)

运行结果如下:

21
21
21
None
10
21
21
None
[1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 21, 1, 1, 1, 25, 1, 1, 1, 1, 5, 10, 1, 1, 1, 10, 1, 1, 1, 1, 5, 10, 21, 1, 1, 10, 21, 1, 1, 1, 25, 1, 10, 1, 1, 5, 10, 1, 1, 1, 10, 1, 10, 21]

动态规划(Dynamic programming)详解icon-default.png?t=N6B9https://blog.csdn.net/qq_37771475/article/details/126855564 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

css滚动条样式指南

css滚动条样式指南 滚动条是网页设计中经常被忽视的元素。虽然它看起来像是一个小细节&#xff0c;但它在网站导航中起着至关重要的作用。默认的滚动条可能看起来不合适&#xff0c;有损整体美观。本文将介绍如何使用 CSS 自定义滚动条。 在 Chrome、Edge 和 Safari 中设置滚…

机器学习笔记之优化算法(六)线搜索方法(步长角度;非精确搜索;Glodstein Condition)

机器学习笔记之优化算法——线搜索方法[步长角度&#xff0c;非精确搜索&#xff0c;Glodstein Condition] 引言回顾&#xff1a; Armijo Condition \text{Armijo Condition} Armijo Condition关于 Armijo Condition \text{Armijo Condition} Armijo Condition的弊端 Glodstein…

【限时优惠】红帽openstack管理课程(CL210) 即将开课

课程介绍 通过实验室操作练习&#xff0c;学员将能够深入学习红帽企业 Linux OpenStack 平台各服务的手动安装方法&#xff0c;还将了解 OpenStack 开发社区的未来发展计划。 培训地点&#xff1a; 线下面授&#xff1a;苏州市姑苏区干将东路666号401室&#xff1b; 远程…

Arcgis地图实战一:单个图层中设施的隐藏及显示

文章目录 1.效果图预览2.弹框的实现3.显示及隐藏的实现 1.效果图预览 2.弹框的实现 let alert this.alertCtrl.create();alert.setTitle(请选择设施);for (let item of this.ctralllayers) {alert.addInput({type: checkbox,label: item.name,value: item.id,checked: item.vi…

二、数据结构10:堆 模板题+算法模板(堆排序,模拟堆)

文章目录 算法模板堆题目代码模板堆的原理down操作理解&#xff1a;up操作理解建堆操作关于heap_swap中存的映射数组理解&#xff08;模拟堆题目中用到&#xff09; 模板题堆排序原题链接题目思路题解 模拟堆原题链接题目思路题解 算法模板 堆题目代码模板 // h[N]存储堆中的…

2023年FPGA好就业吗?

FPGA岗位有哪些&#xff1f; 从芯片设计流程来看&#xff0c;FPGA岗位可以分四类 产品开发期&#xff1a;FPGA系统架构师 芯片设计期&#xff1a;数字IC设计工程师、FPGA开发工程师 芯片流片期&#xff1a;FPGA验证工程师 产品维护期&#xff1a;FAE工程师 从行业上来说&#x…

前端学习——Vue (Day9)

Pinia 快速入门 https://pinia.vuejs.org/zh/getting-started.html npm install pinia import { createApp } from vue import { createPinia } from pinia import App from ./App.vueconst pinia createPinia() const app createApp(App)app.use(pinia) app.mount(#app)&l…

Array.prototype.slice.call()方法详解

slice:用来截取截取字符串方法Array: javascript的一个引用类型&#xff0c;其原型prototype上有一个方法叫slicecall和apply &#xff1a; 用来改变对象中函数内部的this引用&#xff0c;使得函数可以随便换‘妈妈’ 为什么不直接用 arguments.slice(1)呢 不是一样的么? 答案…

消息中间件应用场景介绍

提高系统性能首先考虑的是数据库的优化&#xff0c;但是数据库因为历史原因&#xff0c;横向扩展是一件非常复杂的工程&#xff0c;所有我们一般会尽量把流量都挡在数据库之前。 不管是无限的横向扩展服务器&#xff0c;还是纵向阻隔到达数据库的流量&#xff0c;都是这个思路。…

最新版本mac版Idea 激活Jerbel实现热部署

1.环境准备 1.安装docker desktop 客户端创建本地服务 2.创建guid 3.随便准备一个正确格式的邮箱 2.具体操作 1.通过提供的镜像直接搭建本地服务 docker pull qierkang/golang-reverseproxy docker run -d -p 8888:8888 qierkang/golang-reverseproxy2.guid 通过如下网址直…

使用docker搭建nacos

使用docker搭建nacos docker搭建最新版nacosMySQL下简历nacos配置数据表拉取镜像创建挂载目录启动容器访问nacos docker搭建nacos 2.0版本 docker搭建最新版nacos 最近想在自己服务器上搭建一个nacos服务&#xff0c;以前一直在本地的windows上使用&#xff0c;而且使用着naco…

iOS 搭建组件化私有库

一、创建私有库索引 步骤1是在没有索引库的情况下或者是新增索引的时候才需要用到&#xff08;创建基础组件库&#xff09; 首先在码云上建立一个私有库索引&#xff0c;起名为SYComponentSpec 二、本地添加私有库索引 添加私有库索引 pod repo add SYComponentSpec https:/…

docker容器的基本操作

一、查看Docker的版本信息 [roothuyang1 ~]# docker version 二、查看docker的详细信息 [roothuyang1 ~]# docker info 三、Docker镜像操作 Docker创建容器前需要本地存在对应的镜像&#xff0c;如果本地加载不到相关镜像&#xff0c;Docker默认就会尝试从镜像仓库https://hu…

cc2652主协处理器分时控制同一个外设的问题

问题已提交TI论坛&#xff0c;我是提交到的中文论坛&#xff0c;然后fae给转到英文论坛了。 简单描述就是&#xff0c;怎么让这个单片机一会用主处理器控制SPI设备&#xff0c;一会再用协处理器控制同一个设备。 主处理器的spi配置使用 CCS studio配置的 协处理器使用Sensor Co…

【python】我用python写了一个可以批量查询文章质量分的小项目(纯python、flask+html、打包成exe文件)

web 效果预览&#xff1a; 文章目录 一、API 分析1.1 质量分查询1.2 文章url获取 二、代码实现2.1 Python2.11 分步实现2.12 一步完成2.13 完整代码 2.2 python html2.21 在本地运行2.22 打打包成exe文件2.23 部署到服务器 一、API 分析 1.1 质量分查询 先去质量查询地址&a…

uniapp app端 echarts 设置tooltip的formatter不生效问题以及解决办法

需求一&#xff1a; y轴数据处理不同数据增加不同单位 需求二&#xff1a; 自定义图表悬浮显示的内容 需求一&#xff1a;实现方式 在yAxis里面添加formatter yAxis: [{//y轴显示value的设置axisLabel: {show: true,formatter (value, index) > {var valueif (value > 1…

Jmeter用于接口测试中,关联如何实现

Jmeter用于接口测试时&#xff0c;后一个接口经常需要用到前一次接口返回的结果&#xff0c;应该如何获取前一次请求的结果值&#xff0c;应用于后一个接口呢&#xff0c;拿一个登录的例子来说明如何获取。 1、打开jmeter, 使用的3.3的版本&#xff0c;新建一个测试计划&#x…

抄写Linux源码(Day6:读闪客文章第一回 “最开始的两行代码”)

按照 Day1 完成了 Linux0.11 的运行之后&#xff0c;可以在 ~/oslab/linux-0.11 找到 linux0.11 的源码 根据闪客的文章第一回&#xff1a;https://mp.weixin.qq.com/s/LIsqRX51W7d_yw-HN-s2DA Linux0.11 的启动代码程序入点在 bootsect.s 里&#xff0c;总共 512 个字节 这…

烘焙小程序蛋糕店烘焙店源码点心店小程序源码

本系统开发使用JAVA技术栈开发 使用uniapp技术栈 支持微信小程序 &#xff0c;对接打印机&#xff0c;对接第三方同城跑腿平台 用户端使用&#xff1a;uniapp 管理端使用&#xff1a;vueelementui 后台服务使用&#xff1a;springbootjpa

Web课堂笔记

Web课堂笔记 文章目录 Web课堂笔记第一周html部分CSS部分php部分 第二周B/S工作原理http协议**块标记** 第三周标准盒状模型标签优先级**伪类选择器**伪元素派生选择器 第四周Flex布局多媒体查询下拉菜单作业 第五周创建一个NodeLocalStorage 和 SessionStorge 异同JQuery作业 …