问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

结合代码示例解析递归的工作原理

创作时间:
作者:
@小白创作中心

结合代码示例解析递归的工作原理

引用
1
来源
1.
https://www.freecodecamp.org/chinese/news/what-is-recursion/

原文:How Does Recursion Work? Explained with Code Examples

在本文中,你将学习递归的工作原理。

在学习递归之前,你需要很好地理解函数的工作原理。我在本文中使用了 Python 代码作为示例,因为它的语法简单,但递归的概念对于每种编程语言都是一样的。

什么是递归?

在大多数编程语言中,一个函数可以调用另一个函数。但一个函数也可以调用自身。递归是一种函数调用自身的技术。

这是一个例子:

def call_me():
    call_me()

在这里,函数调用自身,这就是所谓的递归。

但是“调用自身”只是递归的程序定义。递归涉及将一个问题分解为更小的部分,直到无法进一步分解为止。你解决小问题并将它们组合起来以解决整个问题。

用真实生活中的例子类比递归

让我们通过一个例子来理解递归如何真正工作。

想象一下,你在迪士尼乐园排队等候,你不知道前面有多少人。

为了找出答案,你问你前面的人。

试图找出排在你前面的人数

那个人也不知道,所以她们询问她们前面的那个人。

这个过程继续进行,直到问题到达队伍最前面的人,他们看到前面没有人,回答说前面有零人。

然后队伍开始将回复传递回去。每个人在将信息传递回去之前,都会在他们被告知的数字上加 1。

当排在最前面的人回答,“前面有 0 人”时,下一个人加 1 然后回答,“前面有 1 人”,依此类推。

每个人都知道他们前面有多少人

当回复到达直接在你前面的人时,他们再加上 1 并告诉你。通过这种方式,你可以通过将前面的人给的数字添加1,来确定你在队伍中的位置。

这个例子说明了递归如何将问题分解为更小的子问题,然后结合它们的解决方案以解决原始问题。

队伍中的每个人代表同一问题的一个较小实例:确定前面的人数。通过解决这些较小的实例并结合它们的结果,整个问题得到了解决。这正是递归的工作原理。

递归的技术细节

在编码递归时最重要的是要找出:

  • 递归情况:我们可以做的最小工作。在上面的例子中,询问你前面的人他们前面有多少人是我们可以做的最小工作。

  • 基本情况:无需工作的条件。在上面的例子中,队伍最前面的人没有必要问任何问题,因此这是无需工作的条件。

递归的简单例子

计算阶乘是递归的最简单例子,它将真正帮助你理解其工作原理。

有很多方法可以计算一个数的阶乘。但在这里,我们将看到递归的方式来找到它。

在思考我们如何做到这一点之前,我们需要知道一个数的阶乘是什么。

一个数的阶乘是从1到该数的所有数字的乘积。

例如,5的阶乘是120—— 即5×4×3×2×1

我们还可以用数学方式表示如下:

5×(5−1)!

这意味着如果我们知道

(5−1)!

的值,我们就可以通过简单地将5乘以它来轻松得到阶乘。

这就是我们如何找到43210的阶乘:

Factorial of 4 = 4×(4−1)!
Factorial of 3 = 3×(3−1)!
Factorial of 2 = 2×(2−1)!
Factorial of 1 = 1
Factorial of 0 = 1

通过观察这些,很明显要找到5的阶乘,我们必须将5乘以

4!

更普遍的例子

要找到

n

的阶乘,我们需要将

n

乘以

(n−1)!

。这就是你需要递归执行的过程。

现在,必须为递归设置一个停止条件。停止条件是我们不再执行其他操作的地方。当

n

10时,我们可以简单地停止递归,因为这些值的阶乘是已知的。我们可以简单地说1的阶乘是1,对于0也是如此。

因此,分解下来,要找到 n 的阶乘,所需做的最小工作量是

n×(n−1)!

。当我们找到10的阶乘时,我们可以停止对它进行操作。

让我们看看代码是怎样的:

# 计算 n 的阶乘
def fact(n):
    # 基本情况
    if n == 0 or n == 1:
        return 1
    # 递归情况
    else:
        return n * fact(n - 1)

n = 5
# 计算阶乘
factorial = fact(n)
print(factorial)

输出:

120

让我们看看它是如何工作的:

在第一次函数调用中,计算了5的阶乘。接着在第二次调用中,计算了4的阶乘,以此类推,直到计算2的阶乘。

递归计算 5 的阶乘

在调用2的阶乘时,我们有

2×fact(2−1)

,即

2×fact(1)

这达到了我们的基本条件。因此,递归停止,

2×fact(1)

返回

2×1

给前一个函数调用,并且结果从堆栈中弹出。

第四次函数调用返回 2 给前一个函数调用并从堆栈中弹出

类似地,这里是其他内容的计算方式:

第三次函数调用返回 6 给前一个函数调用并从堆栈中弹出

第二次函数调用返回 24 给前一个函数调用并从堆栈中弹出

第一次函数调用返回 120 给初始函数调用并从堆栈中弹出

所以函数最终将值120返回给初始函数调用。

为什么我们需要一个基本条件?

在上面的例子中,我们为代码使用了停止条件。但是如果我们不添加停止条件,或者我们编写的函数永远不满足停止条件呢?

代码会永远运行吗?

不会 – 即使你不终止,代码也不会永远运行。让我们通过一个例子来理解为什么会这样。

def print_five():
    print(5)
    # 调用自身
    print_five()

# 函数调用
print_five()

输出:

5
5
5
...
RecursionError: maximum recursion depth exceeded

如果运行上面的代码,你会看到函数不会永远运行,并以消息

RecursionError: maximum recursion depth exceeded

结束。

当一个函数被调用时,它会被存储在一个调用堆栈中。下面是函数

print_five()

第一次被调用时在调用堆栈中的存储方式。

第一次函数调用时的调用堆栈

函数一次又一次地调用自身,并且每次调用时,函数被存储在调用堆栈中。

n 次函数调用后的调用堆栈

但是调用堆栈的大小是有限的,不能存储无限数量的函数。

调用堆栈空间不足导致栈溢出

当堆栈已满时,它无法再容纳更多调用,导致栈溢出错误。

因此,基本条件对于防止此类错误并确保递归正确终止是必不可少的。

现在让我们看看另一个例子,以便更深入地理解递归。

如何检查一个单词是否是回文

在深入代码之前,你应当知道什么是回文。回文是指正向和反向读取都相同的单词。

例如,

racecar

正向和反向读取都是相同的。

要检查一个单词是否是回文,我们需要检查第一个和最后一个字母是否相同。如果相同,我们接着检查第二个和倒数第二个字母是否相同。

检查 racecar 的第一个和最后一个字符是否相同

racecar

这一例子中,第一个和最后一个字母是相同的,所以我们检查第二个和倒数第二个字母是否相同。它们相同,所以现在我们检查第三个和倒数第三个字母是否相同。现在只剩下一个字母需要检查。一个单一字母始终是回文,因为它正反读都是相同的。

如何检查 racecar 是否是回文

所以,现在让我们尝试以递归的方式思考,包括最少的工作量以及确定何时不需要工作。

最少工作量

检查第一个和最后一个字母是否相同,如果相同,则移除单词中的第一个和最后一个字母。

不需要工作

当只剩下一个或没有字母时,我们可以简单地说它是一个回文。

现在,让我们看看代码是什么样子的:

# 检查回文
def check_palindrome(text):
    # 停止条件
    # 如果文本长度是 1 或 0,返回真
    if len(text) == 0 or len(text) == 1:
        return True
    # 最少工作量
    # 检查第一个和最后一个字符是否相同
    # 如果相同,移除字符串中的第一个和最后一个字符
    if(text[0]==text[-1]):
        return(check_palindrome(text[1:-1]))
    else:
        return False

# 检查字符串是否为回文
text = "racecar"
is_palindrome = check_palindrome(text)
print(is_palindrome)

以下是上述代码的工作原理:

检查 racecar 是否为回文

检查 racecar 是否为回文

何时使用递归

递归看似优雅且简单,但由于反复向堆栈添加方法带来的 CPU 开销,即便是简单问题常也需经过多个步骤方能解决。因此,在使用之前,请确保仔细考虑它是否为解决问题的正确方案。

当代码需要多个循环且显得混乱时,递归可以提供更清晰的解决方案。然而,是否使用递归取决于具体的代码以及所涉及的数据类型或数据结构。对于树和图这类数据结构,递归尤其有用。

尽管递归表面简单,但可能难以理解,即使处理简单问题也可能需要多个步骤。因此,再次提醒,一定要根据具体情况考虑使用。

总结

这只是递归的入门介绍。递归的应用场景很多,你可能对其工作原理感到困惑。我将在下一篇文章中讨论更多递归的高级示例。

顺便说一下,以下是我在学习递归过程中找到的简单且有用的资源:

  • freeCodeCamp 的递归视频:我必须感谢 freeCodeCamp,他们优秀的递归视频课程在我写这篇文章时给我了极大的启发。

  • Programiz Pro 的递归课程:另一个好资源是 Programiz 的递归课程。这是一个高级课程,需付费,但它经过精心设计。而且,你可以直接在他们的平台上进行练习,真的是很值得。

无论你从哪里学习,都不要花太多时间寻找完美的资源。掌握概念并开始练习——这是你唯一能真正学会的方法。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号