CC++ & Algorithm

递推,编程里那块最被低估的万能积木

2026年7月31日关联知识点:Python递推思想4 次阅读

编程里最强大的思想,往往不是那些高深的算法,而是像递推这样朴素到容易被忽略的思维方式。它不需要你懂什么数学天赋,也不涉及复杂的数据结构——只要你能回答两个问题:起点是什么?下一步怎么走? 接下来,计算机就能替你完成所有重复劳动。

很多人第一次听说“递推”,是在一道“兔子繁殖”的题里。但实际上,它早就在你的生活里反复出现。比如你每天爬楼梯:从 1 楼出发,每次上 1 层。那么你的状态变化就是:

1 楼 → 2 楼 → 3 楼 → ...

每一次的新位置 = 旧位置 + 1。这就是递推。在代码里,递推的骨架永远是同一个循环模式:

current = initial
while 还没结束:
    next_value = 根据规律(current)
    current = next_value

这里的关键动作,是每次算完新值后,把新值变成旧值。很多人写不好递推,就是在这一步掉了链子。

一、递推关系式:那把“钥匙”

递推关系式,是告诉你“怎么从前面的结果得到后面的结果”的规则。它像一把钥匙,有了它,计算机才能自动往下算。

比如数列:

3, 6, 9, 12, 15, ...

观察相邻两项的差,全是 3。所以递推关系是:

第 n 项 = 第 n-1 项 + 3
初始条件:第 1 项 = 3

但如果给你:

1, 4, 9, 16, 25, ...

这是平方数,每一项直接等于 n*n。注意,它没有使用前一项,所以严格说不是递推关系式。不过,你可以换个角度:相邻两项的差是 3, 5, 7, 9...(连续奇数),于是也能写成:

第 n 项 = 第 n-1 项 + (2n-1)

这样它就变成了递推。

新手最容易犯的一个错误,是看到一个数列就急着找“通项公式”,结果写出来的式子根本不依赖前一项。记住,递推必须有前一项或前几项的参与。

有一道经典题,专门考察这种构造递推的能力:

输入一个自然数 n。允许在它的左边加上一个不超过原数一半的自然数,然后对新数继续做同样的操作,直到不能再加为止。问一共能得到多少个数?

比如 n=6,能产生 6, 16, 26, 36, 126, 136 ... 这样的数。直接枚举很容易乱,但递推却很简单。

f[i] 表示以数字 i 为“基础”时,能产生的数的总数。根据规则,i 本身算一个;它的左边可以加上 1 到 i//2 中的任意一个数 j,加完之后,新数字还能继续产生 f[j] 个结果。于是递推关系式就出来了:

f[i] = 1 + sum(f[1] + f[2] + ... + f[i//2])

初始条件 f[1] = 1,因为数字 1 左边不能加任何数,只有它自己。

代码很短:

n = int(input())
f = [0] * (n + 1)

for i in range(1, n + 1):
    f[i] = 1 + sum(f[1 : i // 2 + 1])

print(f[n])

这道题最妙的地方在于,你不需要真的“构造出”那些数,只要找到数量之间的递推关系,剩下的交给循环。这就是递推的威力——从结果的角度思考问题,而不是模拟过程

二、斐波那契:递推的经典名片

如果说递推有一座圣殿,那必然是斐波那契数列。

兔子问题的版本很多,但核心是同一个故事:一对刚出生的兔子,第二个月成熟,第三个月开始每月生一对,问第 n 个月有多少对兔子。得到的数列是:

1, 1, 2, 3, 5, 8, 13, 21, ...

从第三项开始,每一项等于前两项之和:

F(n) = F(n-1) + F(n-2)
F(1) = 1, F(2) = 1

代码只需要三个变量:

a, b = 1, 1
for i in range(3, 21):
    c = a + b
    a, b = b, c
    print(c)

这里要特别提醒:递推关系里的运算符号绝不能想当然。加法、乘法、减法看起来都“像”,但结果天差地别。斐波那契的递推是加法,因为每月的兔子数等于前两个月之和。如果你写成乘法或减法,数列很快就不成立了。

这也是很多选择题爱挖的坑:问斐波那契的递推关系,四个选项分别写 +1前两项之和前两项之积前两项之差。只要抓住定义——从第三项开始,每一项等于前两项之和——就不会错。

三、新手最容易踩的三个坑

我见过太多人栽在下面这三件事上,每一个都值得写成血泪教训。

坑 1:忘记更新旧变量

first = 1
for i in range(1, 10):
    next_number = first + 1
    print(next_number)
    # 忘写了 first = next_number

这段代码会一直输出 2,因为你永远在用旧值 first=1 算新值,没有把新值“转移”成旧值。递推的核心就是滚动更新,这一步是灵魂。

坑 2:初始条件错了

比如斐波那契,如果你把初始条件写成 F(1)=0, F(2)=1,整个数列就变成了 0,1,1,2,3... 虽然看起来也像,但那是另一个数列了。每个递推问题都像开锁,钥匙孔必须对准。初始条件就是你插入锁孔前的那次定位——对不上,后面全白费。

有一个判断题说得很对:递推算法在求解问题时,必须明确初始条件(边界值)。 这句话是对的。因为递推关系式只是一条规则,没有初始条件,它无从启动。好比计算阶乘,n! = n × (n-1)!,但如果没人告诉你 0! = 1,这个递推就永远跑不起来。

坑 3:递推关系式写错

这个更隐蔽。有时候你觉得自己找到了规律,但算前两项就露馅了。我的建议是:写完后,先手动算三个例子,看结果是否符合预期。验证是程序员的本能,别偷懒。

四、一个活生生的例子:存钱罐

来一个完整的训练。第一天存 1 元,之后每天比前一天多存 1 元。问第 10 天存了多少、总共存了多少。

这其实是两个递推在并行:

  • 当天存的钱:today = today + 1
  • 总共存的钱:total = total + today
days = 10
today = 1   # 第一天存的钱
total = 0   # 总金额

for day in range(1, days + 1):
    total += today
    print(f"第{day}天:当天存{today}元,总共存{total}元")
    today += 1  # 明天的钱基于今天的钱推出

这个程序虽小,却包含了递推的全部要素:起点(第一天存 1 元)、规律(每天 +1)、重复执行(for 循环)。把它改成 100 天,也就是改一个数字的事。

五、递推之后,通向哪里?

递推本身不复杂,但它是一把钥匙,打开很多看似高大上的门。

  • 递归:和递推正好相反。递推是从边界出发,往前推;递归是从目标出发,往回调用自己。很多递归问题(比如斐波那契)改成递推后效率更高,不会爆栈。
  • 动态规划:递推是动态规划的雏形。动态规划本质上就是“带状态的递推”,只不过多了“最优子结构”“重叠子问题”这些讲究。
  • 数学归纳法:递推的数学根基。你用递推解决问题的过程,其实就是在证明:第一步成立,假如第 k 步成立,那么第 k+1 步也成立。

所以,别觉得递推是个小知识点。你把它吃透了,后面学递归、学动态规划,都会有一种“原来你也在这里”的亲切感。

递推就是编程里的积木——底层只有一块,但只要你找到规律,就能一块接一块地搭出无限可能。下次再遇到“第一天……之后每天都……”这种问题,别忘了你手里有这块万能积木。


关于作者

我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。

这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。

如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

想系统学习这个知识点?查看完整知识点 →