数据结构 | 递归

news2024/11/17 17:29:19

目录

一、何谓递归

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 

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

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

相关文章

本地mvn仓库清理无用jar包

背景 开发java时间久了&#xff0c;本地的m2仓库就会产生很多过期的jar包&#xff0c;不清理的话比较占空间。 原理 是通过比较同一目录下&#xff0c;对应jar包的版本号的大小&#xff0c;保留最大版本号那个&#xff0c;删除其他的。 脚本 执行脚本见文章顶部 执行方式 …

P3374 【模板】树状数组 1 浅谈树状数组 (内附封面)

【模板】树状数组 1 题目描述 如题&#xff0c;已知一个数列&#xff0c;你需要进行下面两种操作&#xff1a; 将某一个数加上 x x x 求出某区间每一个数的和 输入格式 第一行包含两个正整数 n , m n,m n,m&#xff0c;分别表示该数列数字的个数和操作的总个数。 第二…

部署安装私服-Gitlab

一、国内的gitlab是极狐 www.gitlab.cn 国服 www.github.com 国际服 二、国服的gitlab蛮适合中国国情的 1.提交申请可以获得30天的订阅版服务&#xff0c;有需要的话可以先提交一下。订阅后功能多一些。 Gitlab中文官网下载_GitLab免费下载安装_极狐GitLab免…

从零开始学python(十四)百万高性能框架scrapy框架

前言 回顾之前讲述了python语法编程 必修入门基础和网络编程&#xff0c;多线程/多进程/协程等方面的内容&#xff0c;后续讲到了数据库编程篇MySQL&#xff0c;Redis&#xff0c;MongoDB篇&#xff0c;和机器学习&#xff0c;全栈开发&#xff0c;数据分析&#xff0c;爬虫数…

uniapp 视频截图

uniapp 视频截图 本文只针对微信小程序&#xff0c;其他平台并没有测试过&#xff0c;不确定可行性。 微信提供了两个组件可以用来播放视频&#xff1a; live-player: 只要用于实时音视频的播放&#xff08;出于政策和合规的考虑&#xff0c;微信暂时没有放开所有小程序对&l…

地统计学空间插值方法及应用

地统计学 地统计学&#xff0c;是指以具有空间分布特点的区域化变量理论为基础&#xff0c;研究自然现象的空间变异与空间结构的一门学科。它是针对像矿产、资源、生物群落、地貌等有着特定的地域分布特征而发展的统计学。由于最先在地学领域应用&#xff0c;故称地统计学 地…

自学网络安全(黑客)学习心得路线规划

趁着今天下班&#xff0c;我花了几个小时整理了下&#xff0c;非常不易&#xff0c;希望大家可以点赞收藏支持一波&#xff0c;谢谢。 我的经历&#xff1a; 我 19 年毕业&#xff0c;大学专业是物联网工程&#xff0c;我相信很多人在象牙塔里都很迷茫&#xff0c;到了大三大…

html | 无js二级菜单

1. 效果图 2. 代码 <meta charset"utf-8"><style> .hiddentitle{display:none;}nav ul{list-style-type: none;background-color: #001f3f;overflow:hidden; /* 父标签加这个&#xff0c;防止有浮动子元素时&#xff0c;该标签失去高度*/margin: 0;padd…

4年测试工程师,常用功能测试点总结,“我“不再走弯路...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 输入框测试 1、字…

模电专题-MOS管的放大电路分析

在实际应用中&#xff0c;我们经常会使用到功率MOS&#xff0c;这时通常不会将它当成一个开关使用&#xff0c;而是当成一个放大器来使用&#xff0c;那这就需要让其工作在放大状态。 参考下图中的mos管的特性曲线&#xff0c;右图中的输出特性曲线中有一根红色的分界线&#x…

2000-2021年上市公司常用控制变量数据(A股)含stata处理代码

2000-2021年上市公司企业A股常用控制变量 1、时间&#xff1a;2000-2021年&#xff08;注&#xff1a;股权性质从2004年开始&#xff0c;第一大股东持股比例从2003年开始&#xff09; 2、来源&#xff1a;整理自csmar 和wind 3、数据范围&#xff1a;A股公司 不包含已退市的…

使用Python动画粒子的薛定谔波函数(ψ)(完整代码)

使用Python动画粒子的薛定谔波函数&#xff08;ψ&#xff09;&#xff08;完整代码&#xff09; 使用曲柄-尼科尔森方法求解盒子中的粒子 Kowshik chilamkurthy 以后 发表于 书技术 4 分钟阅读 2月 2021&#xff0c; <> 1.4K 5 左图&#xff1a;来源&#xff0c;右图…

Leetcode-每日一题【剑指 Offer 56 - II. 数组中数字出现的次数 II】

题目 在一个数组 nums 中除一个数字只出现一次之外&#xff0c;其他数字都出现了三次。请找出那个只出现一次的数字。 示例 1&#xff1a; 输入&#xff1a;nums [3,4,3,3]输出&#xff1a;4 示例 2&#xff1a; 输入&#xff1a;nums [9,1,7,9,7,9,7]输出&#xff1a;1 限制…

网工必须掌握的5种组网技术,你会了吗?

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、VLAN技术 1、VLAN是什么&#xff1f; 2、VLAN的作用 ①提高网络安全性 ②提高了网络的灵活性性 ③增强了网络的健壮性 二、D…

SPDK的块设备抽象层,从一个简单的示例程序讲起

最早的SPDK仅仅是一个NVMe驱动,但现在的SPDK已经不是原来的SPDK了,其功能涵盖了整个存储栈。为了能够实现丰富的功能,SPDK实现了一个块设备抽象层,其功能与Linux内核的块设备层类似,这个块设备抽象层称为BDEV。 块设备抽象层BDEV在整个SPDK栈中的位置如图所示,它位于中间…

解决一个Yarn异常:Alerts for Timeline service 2.0 Reader

【背景】 环境是用Ambari搭建的大数据环境&#xff0c;版本是2.7.3&#xff0c;Hdp是3.1.0&#xff1b;我们用这一套组件搭建了好几个环境&#xff0c;都有这个异常告警&#xff0c;但hive、spark都运行正常&#xff0c;可以正常使用&#xff0c;所以也一直没有去费时间解决这…

Linux lvs负载均衡

LVS 介绍&#xff1a; Linux Virtual Server&#xff08;LVS&#xff09;是一个基于Linux内核的开源软件项目&#xff0c;用于构建高性能、高可用性的服务器群集。LVS通过将客户端请求分发到一组后端服务器上的不同节点来实现负载均衡&#xff0c;从而提高系统的可扩展性和可…

读磁盘概述

磁盘结构 磁道C 磁头H 扇区S 一个磁盘有很多个盘面&#xff0c;上面是其中一个盘面&#xff0c;每个盘面对应一个磁头。 磁盘的最小单元是扇区&#xff0c;通过CHS可以定位到一个确定的扇区&#xff0c;每个扇区一般是512个字节。 CHS寻道方式 设置好寄存器的值&#xff0c;然…

ElasticSearch可视化管理工具之ElasticHD

推荐的五种客户端 1.Elasticsearch-Head &#xff0c; Elasticsearch-Head 插件在5.x版本之后已不再维护&#xff0c;界面比较老旧。 2.cerebro 据传该插件不支持ES中5.x以上版本。 3.kinaba 功能强大&#xff0c;但操作复杂&#xff0c;以后可以考虑。 4.Dejavu 也是一个 Elas…

Balanced Multimodal Learning via On-the-fly Gradient Modulation

摘要 多模态学习通过整合不同的感官&#xff0c;有助于全面理解世界。因此&#xff0c;多种输入模式有望提高模型的性能&#xff0c;但我们实际上发现&#xff0c;即使多模态模型优于其单模态模型&#xff0c;它们也没有得到充分利用。具体地说&#xff0c;在本文中&#xff0…