python算法(基础)----递归

递归是一种解决问题的方法,将问题分解为更小的子问题,直到得到一个足够小的问题可以被很简单的解决。通常递归涉及函数调用自身。递归允许我们编写优雅的解决方案,解决可能很难编程的问题。

简单例子:计算整数列表和

我们将以一个简单的问题开始,你已经知道如何不使用递归解决。假设你想计算整数列表的总和,例如: [1,3,5,7,9] 。

def listsum(numList):
    theSum = 0
    for i in numList:
        theSum = theSum + i
    return theSum
print(listsum([1,3,5,7,9]))

假设没有 while 循环或 for 循环。你将如何计算整数列表的总和?如果你是一个数学家,你可能开始回忆加法是一个函数,这个函数定义了两个整数类型的参数。故将列表和问题从加一个列表重新定义为加一对整数,我们可以把列表重写为一个完全括号表达式。如下所示:

((((1 + 3) + 5) + 7) + 9)我们也可以把表达式用另一种方式括起(1 + (3 + (5 + (7 + 9))))

注意,最内层的括号(7 + 9)我们可以没有循环或任何特殊的结构来解决它。事实上,我们可以使用以下的简化序列来计算最终的和。

我们如何能把这个想法变成一个 Python 程序? 首先,让我们以 Python 列表的形式重述求和问题。我们可以说列表 numList 的和是列表的第一个元素 numList[0]和列表其余部分 numList[1:] 之和的总和。以函数形式表述:

listSum(numList) = first(numList) + listSum(numList)

在这个方程式中,first(numList)返回列表的第一个元素, rest(numList) 返回除第一个元素之外的所有元素列表。这很容易在 Python 中表示。

def listsum(numList):
    if len(numList) == 1:
        return numList[0]
    else:
        return numList[0] + listsum(numList[1:])
print(listsum([1,3,5,7,9]))

在这个清单中有几个关键地方。首先,在第 2 行,我们检查列表是否为一个元素。这个检查是至关重要的,是我们的函数的转折子句。第二,在第5行函数调用自己!这就是我们称 listum 算法递归的原因。递归函数是调用自身的函数。Figure 1 展示了对列表 [1,3,5,7,9] 求和所需的一系列递归调用。 你应该把这一系列的调用想象成一系列的简化。 每次我们进行递归调用时,我们都会解决一个较小的问题,直到达到问题不能减小的程度。

当我们到达简单问题的点,我们开始拼凑每个小问题的答案,直到初始问题解决。Figure 2展示了在 listsum 通过一系列调用返回的过程中执行的 add 操作。当listsum 从最顶层返回时,我们就有了整个问题的答案。

递归的三定律

像阿西莫夫机器人,所有递归算法必须服从三个重要的定律:

  1. 递归算法必须具有基本情况。
  2. 递归算法必须改变其状态并向基本情况靠近。
  3. 递归算法必须以递归方式调用自身。

整数转换为任意进制字符串

假设你想将一个整数转换为一个二进制和十六进制字符串。例如,将整数 10 转换为十进制字符串表示为 10 ,或将其字符串表示为二进制 1010 。虽然有很多算法来解决这个问题,包括在栈部分讨论的算法,但递归的解决方法非常优雅。让我们看一个十进制数 769 的具体示例。假设我们有一个对应于前 10 位数的字符序列,例如 convString =“0123456789”。通过在序列中查找,很容易将小于 10 的数字转换为其等效的字符串。例如,如果数字为 9 ,则字符串为 convString[9] 或 “9”。如果我们将数字 769 分成三个单个位数字, 7 , 6 和 9 ,那么将其转换为字符串很简单。数字小于 10 听起来像一个好的基本情况。

知道我们的基本情况是什么意味着整个算法将分成三个部分:

  1. 将原始数字减少为一系列单个位数字。
  2. 使用查找将单个位数字数字转换为字符串。
  3.  将单个位字符串连接在一起以形成最终结果。

下一步是找到改变其状态的方法并向基本情况靠近

使用整数除法将 769 除以 10 ,我们得到 76 ,余数为 9 。这给了我们两个好的结果。首先,余数是小于我们的基数的数字,可以通过查找立即转换为字符串。第二,我们得到的商 小于原始数字,并让我们靠近具有小于基数的单个数字的基本情况。现在我们的工作是将 76 转换为其字符串表示。再次,我们使用商和余数分别获得 7 和 6 的结果。最后,我们将问题减少到转换 7 ,我们可以很容易地做到,因为它满足 n < base 的基本条件,其中 base = 10 。请注意,余数位于图右侧框中。

实现上述算法的 Python 代码, 以 2 到 16 之间的任何基数为参数。

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

请注意,在第 3 行中,我们检查基本情况,其中 n 小于我们要转换的基数。当我们检测到基本情况时,我们停止递归,并简单地从 convertString 序列返回字符串。在第 6 行中,我们满足第二和第三定律 - 递归调用和减少除法问题大小。 让我们再次跟踪算法; 这次我们将数字 10 转换为其基数为 2 的字符串(“1010”)。

栈帧:实现递归

假设不是将递归调用的结果与来自 convertString 的字符串拼接到 toStr,我们修改了算法,以便在进行递归调用之前将字符串入栈。

from pythonds.basic.stack import Stack
rStack = Stack()
def toStr(n,base):
    convertString = "0123456789ABCDEF"
    while n > 0:
        if n < base:
            rStack.push(convertString[n])
        else:
            rStack.push(convertString[n % base])
        n = n // base
    res = ""
    while not rStack.isEmpty():
        res = res + str(rStack.pop())
    return res
print(toStr(1453,16))

每次我们调用 toStr,我们在栈上推入一个字符。回到前面的例子,我们可以看到在第四次调用 toStr 之后,栈看起来像 Figure 5 。注意,现在我们可以简单地将字符从栈中弹出,并将它们连接成最终结果 “1010”。

前面的例子让我们了解了 Python 如何实现一个递归函数调用。 当在 Python 中调用函数时,会分配一个栈来处理函数的局部变量。当函数返回时,返回值留在栈的顶部,以供调用函数访问。 Figure 6 说明了第 4 行返回语句后的调用栈。

栈帧还为函数使用的变量提供了一个作用域。即使我们重复地调用相同的函数,每次调用都会为函数本地的变量创建一个新的作用域。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狼性书生

谢谢鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值