1633 字
8 分钟
递归思想

本文许可:转载时转载请注明出处

Python递归思想#

递归是一种编程技术,函数通过调用自身来解决问题。它通常包含基准条件(终止递归)和递归条件(继续调用自身)。#

什么是递归?#

把一个大问题拆解成结构相同但规模更小的子问题,直到触及一个可以直接回答的边界条件(基准情形)。 简单来说:

  • 递归就像查一个词的解释,发现解释里又有一个不认识的词,于是又去查那个词,直到找到一个不需要查其他词就能懂的解释为止。

二、递归三要素#

  1. 基准条件(Base Case) 递归的”紧急出口”。当问题简化到最简形式时,直接返回结果而不再自我调用。
def factorial(n: int) -> int:
# 基准条件:最小子问题,已知答案
if n <= 1:
return 1 # 0! = 1, 1! = 1

关键点:基准条件必须可达,否则会导致无限递归和溢出。 2. 递归条件(Recursive Case) 将问题分解为更小的同类子问题,并向其委托求解。

def factorial(n: int) -> int:
if n <= 1:
return 1
# 递归条件:缩小问题规模,委托给factorial(n-1)
return n * factorial(n - 1)
  1. 回归过程(Unwinding) 子问题解决后,逐层返回并组合结果,最终得到原问题的答案。
# 以 factorial(4) 为例的回归过程演示
factorial(4) # 返回 4 * factorial(3)
└── factorial(3) # 返回 3 * factorial(2)
└── factorial(2) # 返回 2 * factorial(1)
└── factorial(1) # 基准:返回 1
# 开始回归:
└── 返回 2 * 1 = 2
└── 返回 3 * 2 = 6
└── 返回 4 * 6 = 24 # 最终结果

演示#

一个简单的小栗子:

def countdown(n):
# 1. 停止条件(到家了,不用再走了)
if n == 0:
print("时间到!")
return
# 2. 先做事(输出当前数字)
print(n)
# 3. 然后让下一个同学继续(调用自己)
countdown(n - 1)
countdown(3)# 调用:countdown(3)

执行过程就像传包裹: countdown(3) → 打印3 → countdown(2) → 打印2 → countdown(1) → 打印1 → countdown(0) → 打印"时间到!" → 结束 核心就两个规矩

  1. 必须有个终点(比如 n == 0),否则会永远进行下去
  2. 每次都要更接近终点(比如 n-1),否则到不了终点

递归 vs 循环:如何选择?#

哎这时候发现,这递归怎么跟循环这么像啊,没错,递归确实能换成循环,两者都能“重复做事”,但思路相反

对比点循环递归
方向感从前到后一口气跑完先一路到底,再原路返回
暂存中间结果用变量(如 sum)用调用栈(系统自动)
直观程度简单数数时更直接拆套娃问题时更直观
溢出风险几乎无层数太深会栈溢出
对比
total = 0
for i in range(1, 6): # 1 2 3 4 5
total += i
print(total) # 15
def total(n):
if n == 1: # 最小娃
return 1
return n + total(n-1) # 先问下层,再回头加
print(total(5)) # 15

适合用递归的场景:

  • 树形/图形结构遍历(文件目录、组织架构)
  • 分治算法(快速排序、归并排序)
  • 回溯问题(八皇后、迷宫求解)

不适合递归的场景:

  • 简单重复操作(用循环更高效)
  • 深度不可预测的大数据(易栈溢出)
  • Python中的性能敏感场景(无尾递归优化)

原理拆分

  1. 递进(往下套娃) 每次调用自己时,问题变小一点,就像拆开一层俄罗斯套娃
  2. 基准(最小的娃) 当问题小到不能再拆时,直接给出答案(最小的娃里没别的娃了)
  3. 回归(回头组合答案) 拿到最小答案后,一层层往回拼,最终得到大答案

最终原理流程图展示#

def factorial(n):
if n == 1: return 1 # 基准:最小的娃
return n * factorial(n-1) # 套娃
graph TD
    A["factorial(4)"] -->|"4 × ?"| B["factorial(3)"]
    B -->|"3 × ?"| C["factorial(2)"]
    C -->|"2 × ?"| D["factorial(1)"]
    
    %% 基准情形:返回 1
    D -->|"返回 1"| D1["1"]
    
    %% 回归阶段:层层返回并相乘
    D1 -.->|"2 × 1"| C1["2"]
    C1 -.->|"3 × 2"| B1["6"]
    B1 -.->|"4 × 6"| A1["24"]
    
    %% 最终结果说明
    A1 -.->|"最终结果"| R["递归完成<br/>factorial(4) = 24"]
    
    %% 样式定义(使用非保留字名称)
    classDef c1 fill:#ffe599,stroke:#333,stroke-width:2px;
    classDef c2 fill:#9ccff9,stroke:#333,stroke-width:2px;
    classDef c3 fill:#d9ead3,stroke:#333,stroke-width:2px;
    classDef c4 fill:#fff,stroke:#333,stroke-width:1px;
    
    %% 应用样式
    class A,B,C,D c1;
    class D1,C1,B1,A1 c2;
    class D c3;
    class R c4;

经典案例介绍#

斐波那契数列 说到递归思想,总是离不开斐波那契数列 斐波那契数列(Fibonacci sequence),又称黄金分割数列,是一个在数学和自然界中广泛出现的数列。这个数列由意大利数学家列奥纳多·斐波那契(Leonardo Fibonacci)在1202年提出,因此得名。 定义 斐波那契数列的定义如下:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n-1) + F(n-2),对于 n ≥ 2

数列的前几项 根据上述定义,斐波那契数列的前几项是: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, … 简单来说,就是: 从 0 和 1 开始,后面的每个数,都是前两个数相加。 数列: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34…

所以,这里就很适合用递归思想了 于是,我们可以得出:

def fib(n):
"""返回第 n 个斐波那契数,从 0 开始计数。"""
if n < 2: # ① 最最简单的情况
return n
return fib(n-1) + fib(n-2) # ② 自己调用自己 → 这就是“递归”
# 测试
for i in range(8):
print(f'fib({i}) = {fib(i)}')

运行结果

fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
口诀对应代码斐波那契例子
停if n < 2: return n0 和 1 已经最简单,直接给答案
拆fib(n-1) + fib(n-2)把大问题拆成两个“小一号”的问题
信放心地让 fib() 自己去算相信“别人”能把小问题的答案算对

总结#

核心本质 递归不是循环,而是信任的委托链: “我(n层)不知道完整答案,但我知道n-1层会算,我只需要在他结果上+1就行” 每一层只做两件事:

  • 判断:“是不是最小的问题了?“(基准检查)
  • 委托:“不是?那我处理一点点,剩下的交给下一层” 为什么必须回头? 因为下一层的答案依赖于更下一层,必须等到最底层答案返回后,上一层才能计算出结果。就像你不能在拿到子结果前就拼出最终结果。 一句话原理:递归 = 往下问到头,往上答回来。
递归思想
https://mikufans.yuzhiyuan.top/posts/递归思想/
作者
遇缘
发布于
2025-11-19
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 319 天

部分内容可能已过时