递归的应用之字符串反转

Python列表内置了一个可以用来反转列表的方法。假设你要让字符串反转,一种比较高效的办法是,把字符串转换为字符列表,之后反转这个列表,再然后将列表转换回字符串。然而,如果使用递归的话,我们就能够非常轻松地编写出一个直接使字符串反转的函数,而不必用列表来作为中间存储。

这样做的基本思想是:把字符串当作一个递归对象。一个大的字符串是由若干个较小的对象组成的,而且这些对象也是字符串。实际上,一种能够把几乎所有序列都进行拆分的非常方便的方法是:

把它的第一个元素当作单独的存在,而这个元素的后面则恰好紧跟着另一个序列。

在字符串这个情况下,我们可以把它拆分成第一个字符和“所有其他的字符”。这样,如果我们反转字符串里的其余部分,并且把第一个字符放在字符串的结尾处,我们就能够把整个字符串反过来了。让我们把这个算法编写成代码,看看会发生什么:

def reverse(s):
    return reverse(s[1:])+s[0]

注意这个方法的工作原理。切片s[1:]会给出除字符串的第一个字符之外的所有其他字符。然后我们反转这个切片(递归),并且把第一个字符(s[0])连接到这个结果的末尾。用一个具体的例子来进行思考可能会对你的理解更有帮助。假设s是字符串abc,那么s[1:]就是字符串bc。反转它会产生cb,然后再在最后加上s[0],就能够得到cba了。这正好就是我们想要的答案。

然而,遗憾的是,这个功能并不能正常工作。下面是我们尝试运行这段代码时会发生的输出:

  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

这里只显示了一部分输出。整段输出实际上有1000行之多!那么,是什么错误导致了这个问题呢?

回忆一下,我们提到过,为了构建一个正确的递归函数,我们需要有一个不需要继续递归的基本情况,不然的话递归就变成了无限循环。我们在编写函数代码的时候太着急了,以至于我们忘了要包含一个基本情况。因此,我们写的代码实际上是一个无限递归(infinite recursion)。每次调用reverse方法都包含着另一个reverse方法的调用,因此它们会一直执行下去,根本不会返回。当然,每当一个函数被调用的时候,它都会占用一些内存(将参数和局部变量存储在运行时堆栈里),因此这个过程并不能永远地持续下去。Python在第1000次调用之后就终止了这个方法,因为1000是默认的“最大递归深度

让我们回过头来找出一个合适的基本情况。在对序列执行递归时,基本情况通常是空序列或者是仅仅包含一个元素的序列。对于字符串反转问题,我们可以使用空字符串来作为基本情况,因为空字符串是它自己的反转。当递归调用reverse方法的时候,总是会用一个比原始字符串还要短一个字符的字符串。所以,最后我们总会得到一个空字符串。这是reverse方法的正确版本:

def reverse(s):
    if s == "":
        return s
    else:
        return reverse(s[1:]) + s[0]

print(reverse('apple')) #elppa

总结

  • 不能忘记基本情况

你可能感兴趣的:(数据结构,python,python,递归)