探讨递归这个引人入胜的领域

又回来念一念递归的经,编程之旅上有笔直的大路,也有盘一盘的递归? 今天探讨递归是引人入胜的思想 — 递归也许是上帝做工的方式。函数调用自己则体现了内在统一的秩序,

递归究竟是什么?

每年我们都会长一岁,试试定义一个函数 deltaYear只做一件事情,就是在输入的age上的年龄上 +1 (增加 1)

def deltaYear(age):
    if age == 996:return
    #age = 997 就报错!
    
    print('age',age)
    return deltaYear(age+1)
print(deltaYear(age=0))

deltaYear(age)一直不断地调用自己,age变量也随之而变化!

这也是大喵能想到的最简洁的递归栗子,直观地看到 age = 997 时,程序出现错误:

RecursionError: maximum recursion depth 
exceeded while calling a Python object

请参见最后几步的输出:

 ...
 
age 990
age 991
age 992
age 993
age 994
age 995
Traceback (most recent call last): 
  [Previous line repeated 993 more times]
  File "C:\Users\29358\PycharmProjects\Python_workspace-main\4 ddm_course\02_Python inner function\RecursionABC.py", line 7, in deltaYear
    print('age',age)
RecursionError: maximum recursion depth exceeded while calling a Python object

爆栈的次数发生在什么时候?

好了,让我们戴上探险家的帽子。想象一下,你身处一个神奇的森林。是的,编程可以是神奇的!

你偶然发现了一棵会说话的树,它要求你数一下它的树枝上的所有叶子。

但有一个小插曲 — 这棵树的树枝分成了更小的树枝,而那些树枝又分成更小的树枝。简而言之,你需要数在嵌套的叶子中的叶子。

编程中的递归有点像这个情景 — 一个函数调用自身来解决问题,问题会被分成越来越小的部分。

递归函数的要点

那么,你听说过“递归函数”这个术语,它听起来有点神秘,对吧?好吧,让我们来揭开它的神秘面纱。

递归函数就像一个解谜伙伴。它将一个复杂的问题分解为相同问题的简化版本,直到达到基本情况 — 题目中最小的可解部分。

让我们来看一个经典的例子 — 计算阶乘。假设你想找出一个数 n 的阶乘。

你知道 n! 就等于 n 乘以 (n-1)!,而 (n-1)! 又等于 (n-1) 乘以 (n-2)!,以此类推,直到我们得到 1!,它就是 1。

那么,这在 Python 中是这样的:

def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n - 1)

看到了吗?我们使用相同的函数将问题分解成更小的部分,最终达到递归停止的基本情况。

信任之舞:基本情况和递归情况

好了,想象一下,你正在举办一个派对,邀请了一些朋友。你告诉你的朋友邀请他们的朋友,而他们的朋友又邀请更多的朋友。

但是,有个问题 — 你至少需要一个朋友来开始这个连锁。在递归中,这个“一个朋友”就是你的基本情况。如果没有它,你就会陷入无限循环的派对策划中!

在我们的阶乘例子中,基本情况是当 n 等于 1 时。这就是开始派对的朋友。剩下的链条,我们在其中将 n 与 (n-1)! 相乘,就是递归情况。就像每个朋友都邀请他们自己的一组朋友一样。

解开魔法:调用栈

好的,让我们看一看幕后情况。当你在 Python 中调用一个函数时,计算机会创建一个调用栈 — 这是一个函数调用的堆栈的花哨说法。

在我们的递归旅程中,每次调用阶乘函数时,一个新的函数实例都会被添加到堆栈中。但请记住,堆栈的空间是有限的!

所以,当我们深入递归时,堆栈会变得更高。但当我们达到那个神奇的基本情况时,堆栈开始缩小,因为每个函数调用都会完成并返回其值。这就像解开礼物一样 — 你一层一层地剥开,直到达到里面的礼物!

小心:无限陷阱

现在,我的朋友们,在触发递归末日之前,请记住那个至关重要的基本情况。如果你忘了它,你将发现自己陷入无限的函数调用坑中 — 那可不是一个有趣的派对!

总结递归的乐趣

所以,这就是 Python 中递归的世界,一点也不可怕。我们已经解决了基础知识 — 理解递归的概念,区分基本情况和递归情况,以及瞥见调用栈的神奇之处。

递归就像一个解谜巫师,将问题分解成一口口大小的块。只要有正确的基本情况和对如何组合这些块的清晰理解,你将能够创建递归函数,令即使是最智慧的编程树也会赞叹不已。

汉诺塔

MIT的CS课堂对递归的理解并没有用上述的栗子。递归不仅是函数调用自己本身,还有什么精妙之处被以上的栗子忽略?

经典的做法是用汉诺塔理解递归。汉诺塔之所以经典,是因为不同于前面谈到的两个栗子,只包含递归的一个要素:函数自己调用自己。

alt

图示有 8 个Disk,A, B, C三个柱。任务是将A柱的disk全部移动到C 柱即移动到最右侧的柱,还原为A 柱的叠放状态:保持较小的总是在较大的上面。

搬动过程中要满足两个条件,搬动过程中:每次只能搬动一片;每一次搬动保证尺寸大的Disk不能放在小的Disk上面。

试一试看?

自上而下将 8 个disk分别叫1、2 .... 7、8号盘,任务目标是搬动 8 个盘到 C 柱。

那么,任务等价于最先将 8 号盘放在 C的最下面,再将1-7号盘放在 8 之上。

注意我的表述方式,任务分为两个子任务。

def tower_of_hanoi(n, source, auxiliary, target):
    if n == 1:
        print(f"Move disk 1 from {source} to {target}")
        return
    tower_of_hanoi(n-1, source, target, auxiliary)
    print(f"Move disk {n} from {source} to {target}")
    tower_of_hanoi(n-1, auxiliary, source, target)

# 示例用法
tower_of_hanoi(4, 'A''B''C')

输出结果:

Move disk 1 from A to B
Move disk 2 from A to C
Move disk 1 from B to C
Move disk 3 from A to B
Move disk 1 from C to A
Move disk 2 from C to B
Move disk 1 from A to B
Move disk 4 from A to C
Move disk 1 from B to C
Move disk 2 from B to A
Move disk 1 from C to A
Move disk 3 from B to C
Move disk 1 from A to B
Move disk 2 from A to C
Move disk 1 from B to C

C++:

cpp

#include <iostream>

void tower_of_hanoi(int n, char source, char auxiliary, char target) {
    if (n == 1) {
        std::cout << "Move disk 1 from " << source << " to " << target << std::endl;
        return;
    }
    tower_of_hanoi(n-1, source, target, auxiliary);
    std::cout << "Move disk " << n << " from " << source << " to " << target << std::endl;
    tower_of_hanoi(n-1, auxiliary, source, target);
}

int main() {
    int n = 3; // 汉诺塔的盘子数
    tower_of_hanoi(n, 'A''B''C');
    return 0;
}

这两个示例代码都实现了汉诺塔问题的解决方案。在这个问题中,有三个杆(通常称为A、B和C),以及一些不同大小的圆盘,开始时都放在一个杆上。

目标是将所有圆盘从起始杆移动到目标杆,遵守以下规则:

一次只能移动一个盘子。

任何时候,大盘子不能放在小盘子的上面。

这两个示例都使用递归来解决问题,因为汉诺塔问题本身就是递归性质的经典问题。这些代码将打印出移动每个盘子的步骤,以完成整个汉诺塔谜题。

当你解决汉诺塔问题时,搬动的总次数可以通过数学公式来计算,而不需要实际模拟每一步的搬动。下面是一个计算搬动次数的Python程序:

def hanoi_moves(n):
    if n == 1:
        return 1
    else:
        return 2 * hanoi_moves(n - 1) + 1

#示例用法

num_disks = 3  # 汉诺塔的盘子数
total_moves = hanoi_moves(num_disks)
print(f"移动的总次数:{num_disks} disks: {total_moves}")

这个程序使用递归方式计算汉诺塔问题所需的总搬动次数。

公式为:Total moves = 2**n - 1

alt

其中,n 是汉诺塔的盘子数。

在示例用法中,我们计算了有3个盘子的汉诺塔问题所需的总搬动次数。你可以根据需要更改 num_disks 的值来计算其他盘子数量的搬动次数。

本文由 mdnice 多平台发布

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值