前言:为什么从一个数列开始
你大概见过这串数。
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …
第一项是 0,第二项是 1,之后每一项都等于前两项相加。0 加 1 是 1,1 加 1 是 2,1 加 2 是 3,2 加 3 是 5。一个会做加法的小孩能把它续写下去,写一整天都不需要乘除。
但是这串数不太安分。
你拿它写一段最直接的递归程序,它会反过来教你:看上去最像数学定义的程序,跑起来最不像样。
你拿它数一段走廊能用多少种方式铺砖,它会从你手里那一块块砖里长出来。
你把它装进一个 2 × 2 2 \times 2 2 × 2 的小方块,它就变成"状态怎么从这一步变到下一步"。
你问它第 n n n 项到底是多少,它会从一串整数里拿出一个 5 \sqrt{5} 5 给你看。
你只盯着它的最后一位数字,它会像钟表一样循环,60 步回到原地。
你抛硬币,要求不出现连续两个正面,它又一次跑出来,当起了计数员。
这么多门,都被同一串数推开。
听起来像在讲不同的故事。一段慢程序、一条铺砖走廊、一个 2 × 2 2 \times 2 2 × 2 矩阵、一个 5 \sqrt{5} 5 、一个 60 步的循环、一个抛硬币的游戏。它们都从同一条小规则里长出来:每一项等于前两项之和。
这本书想讲的就是这件事。
黄金比例会出现,但不在这本书的中心
黄金比例 φ = 1 + 5 2 \varphi=\frac{1+\sqrt5}{2} φ = 2 1 + 5 这本书里会出现。它必须出现,因为它和斐波那契数列真正连在一起:相邻两项之比会越来越靠近 φ \varphi φ ,闭式公式里也直接写着它。这部分很美,我们会慢慢看清楚它是怎么进来的。
φ \varphi φ 之外,松果、向日葵、鹦鹉螺、希腊神庙、达·芬奇素描、各种宽长比的人脸,这些就不在这本书里了。它们都好看,也都真实存在,但停在这一步有点亏。
数学最好看的部分,常常在"它为什么会这样"。一个现象只被展示成奇迹,我们能欣赏一秒。一个现象被拆开看清楚机制,我们能带走它一辈子。
所以黄金比例会出现,作为整条故事线里的一个角色,在它该出现的位置出现。
这本书真正要看什么
同一个递推 F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} F n = F n − 1 + F n − 2 可以被写成定义、被执行成程序、被存成一张表、被加速成对数时间的算法、被压缩进一个 2 × 2 2 \times 2 2 × 2 矩阵、被解成带有 5 \sqrt{5} 5 的闭式公式、被一段一段铺成砖、被画成点和线、被追问整除性、被模掉一个数看余数、被嵌进一个概率分子里。
这些是同一种结构换了十一种说法。
每一种说法背后,是一种数学分支在问自己的问题。
程序会问:怎么算?算法会问:什么工作在重复,怎么省掉它?矩阵会问:状态怎么从这一步变到下一步?闭式公式会问:能不能直接跳到第 n 项?组合数学会问:这个数到底在数什么?图论会问:能不能把它画成点和线?数论会问:整数运算里留下了什么规律?模运算会问:只看余数会怎样?概率会问:所有可能里,有多少个是我们想要的?
斐波那契数列的好处是:换一个问题,不用换对象。
它够小,能放在脑子里。它够厚,能撑住一整本这种来回翻译的练习。
学完之后,你不会变成斐波那契专家。但你会比以前更习惯一件事:数学里那些被切成不同课程的内容,其实从来没有真正分开过。它们只是同一种结构被不同的人用不同的口音说出来。
一个小例子,先垫垫底
讲一个具体一点的,好让你知道这本书喜欢什么样的瞬间。
想象一条长度为 4 的窄走廊,你要用 1 格砖和 2 格砖把它铺满。问有多少种铺法。
我们干脆把所有铺法列出来:
1+1+1+1
1+1+2
1+2+1
2+1+1
2+2
一共 5 种。
这个 5 是五种你能亲手摆出来的铺法。你能拿五张纸各画一种,铺在地上对比。
现在再看一条长度为 n n n 的走廊。最后一块砖要么是 1 1 1 格,要么是 2 2 2 格。如果最后一块是 1 1 1 格,前面剩下长度 n − 1 n-1 n − 1 的走廊,铺法数就是长度 n − 1 n-1 n − 1 的铺法数。如果最后一块是 2 2 2 格,前面剩下长度 n − 2 n-2 n − 2 的走廊,铺法数就是长度 n − 2 n-2 n − 2 的铺法数。
所以总数满足 A ( n ) = A ( n − 1 ) + A ( n − 2 ) A(n) = A(n-1) + A(n-2) A ( n ) = A ( n − 1 ) + A ( n − 2 ) 。它就是斐波那契递推。
这一次,斐波那契递推从"最后一块砖到底是 1 格还是 2 格"这个问题里长出来。
这本书喜欢的就是这种时刻:一个具体的小例子,一个简单的分情况,一个熟悉的结构突然冒出来。
比魔术更稳,也比魔术更耐看。
这本书的结构
全书分前言、二十一章正文、尾声。
第一部分"递推"是第 1 到 6 章。第 1 章从兔子、楼梯、铺砖三个场景长出递推。第 2 章把这段递推翻译成递归程序,跑几个例子,第一次感觉慢。第 3 章展开调用树,分析慢的原因。第 4 章给程序装上记忆,把指数级压成线性。第 5 章让程序跳步,用快速倍增做到对数时间。第 6 章再用矩阵快速幂,给出另一种对数时间算法。
第二部分"换语言"是第 7 到 15 章,九章六种语言。第 7 章用矩阵看(状态视角),第 8 章用闭式公式看(推导 Binet 公式),第 9 章追问 5 \sqrt{5} 5 怎么消失,第 10 章用铺砖和楼梯的计数看,第 11 章把计数推广到子集和二进制串,第 12 章用图看,第 13 章用恒等式的代数证法看,第 14 章用恒等式的组合证法看,第 15 章用生成函数看。
第三部分"整数性质"是第 16 到 19 章。第 16 章看整除性,第 17 章看模周期(发现循环),第 18 章解释为什么是 60(素数情况与中国剩余定理),第 19 章看 Zeckendorf 表示,用斐波那契数反过来表示任意整数。
第四部分"推广"是第 20 和 21 章。第 20 章把斐波那契搬到抛硬币概率里,第 21 章问"等到首次出现连续两个正面平均要抛几次",并推广到 k-bonacci。尾声把视角拉到一般线性递推,回到"换语言"这条主线。
每章大约三四千字。可以按顺序读,也可以跳着读。但建议前六章按顺序,因为后面反复用到第 2、3、4 章里建立的几个工具。
一句关于精度的说明
不同书对斐波那契数列的起点写得不一样。有的从 F 1 = 1 , F 2 = 1 F_1 = 1, F_2 = 1 F 1 = 1 , F 2 = 1 开始,有的从 F 0 = 0 , F 1 = 1 F_0 = 0, F_1 = 1 F 0 = 0 , F 1 = 1 开始。
本书统一用 F 0 = 0 , F 1 = 1 F_0 = 0, F_1 = 1 F 0 = 0 , F 1 = 1 。这样写的好处是和矩阵、闭式公式、组合计数都对得整齐。代价是某些常见说法要平移一下:比如"爬 n n n 阶楼梯的方法数等于 F n + 1 F_{n+1} F n + 1 "而不是 F n F_n F n 。索引看起来差 1 1 1 ,但里面的逻辑是稳的。
这种细节在书里第一次出现时会说清楚。之后用到就不再重复。
小数字是护栏。每次写完一个公式,我会拿 n = 1 , 2 , 3 n = 1, 2, 3 n = 1 , 2 , 3 代进去试一下。如果对不上,我们就回去修索引,而不是挥手遮过去。
这是数学的一部分。
最后说一下语气
我尽量不用"神奇""奥秘""宇宙密码"这一类词。它们把数学讲成巫术,听起来热闹,听完什么也带不走。
我尽量不用"本章我们将""由此可见""具有重要意义""我们可以得出三点启示"这种教材腔。它们把数学讲成文件,读起来像在签合同。
我想写的语气大概是这样:
"你看,这儿有个小机关。我们把它拆开看看。拆完你会发现,它还连着后面那一串东西。"
仅此而已。
开始
我们从最普通的一个动作开始:问下一项从哪儿来。
一个当前的数,可能由前面几个更小的数决定。一个大问题,可能可以拆成两个更小的问题。最后一步,可能就解释了整个递推。
这是第一扇门。
推开门,后面那些门会一扇一扇亮起来。
第 1 章 一个递推是怎么长出来的
1202 年,比萨的莱昂纳多写了一本叫《计算之书》的著作。莱昂纳多有个外号叫 Fibonacci,意思是波那契的儿子。这本书把印度-阿拉伯数字和算法介绍到欧洲,影响很大。书里第 12 章有一个问题,大意是这样:
有人把一对刚出生的小兔子放进一个围起来的地方。小兔子要一个月长大,长大之后每个月生一对新的小兔子。假设兔子都不死,问一年后有多少对兔子。
我们一格一格地数。
第 0 个月:1 对小兔子。
第 1 个月:还是 1 对,但它们已经长大。
第 2 个月:这对大兔子生了 1 对小兔子,所以总共 2 对。
第 3 个月:原来的大兔子再生 1 对小兔子,上个月的小兔子刚长大还不生,所以总共 3 对。
第 4 个月:两对大兔子各生一对,上个月新生的还太小不生,所以总共 5 对。
继续下去:8 对、13 对、21 对、34 对。
这串数就是 1, 1, 2, 3, 5, 8, 13, 21, 34。
兔子故事本身不重要。重要的是它怎么算出来的。
把"本月怎么从上月变来"分成两种情况
每个月的兔子总数,可以分成两群:上个月就已经在的兔子,加上这个月新生的兔子。上个月就已经在的兔子,这个月都还活着(题目假设兔子不死),所以这一群就是上个月的总数。
新生兔子有多少对?等于上个月已经长大、可以生小兔子的兔子对数。哪些兔子在上个月已经长大?再上一个月就已经在的所有兔子。因为它们要么当时已经长大,要么上个月从小变大会在下个月开始生。所以"新生兔子对数"等于"再上一个月的兔子总数"。
把这个分情况用一句话写出来:
本月总数 = 上月总数 + 再上月总数。
如果记第 n n n 个月的兔子对数为 R n R_n R n ,那么 R n = R n − 1 + R n − 2 R_n=R_{n-1}+R_{n-2} R n = R n − 1 + R n − 2 。兔子数列从 1 , 1 1,1 1 , 1 开始,整个故事就被一行式子压缩完了。
这是斐波那契递推的第一次出现。它从"今天和昨天有什么关系"分成两种情况,然后写下来。
把这个动作看清楚
刚才那段推导里藏着一个动作。这个动作后面会反复用到,所以这里值得把它讲清楚。
我们想要的是"第 n 个月有多少对兔子"。我们没有直接去算它。我们把它拆成了两种情况:上个月就已经有的兔子,加上这个月新生的兔子。这两种情况加起来就是全部兔子,而且不重不漏。
拆完之后,"上个月就已经有的兔子"等于"上月总数",这没什么好说的。"这个月新生的兔子"看起来要再分析一遍,但好在它只依赖"再上一个月的兔子数",比 n 小,所以可以把这个子问题的答案直接拿来用。
把一个大问题拆成几个小问题,每个小问题的规模都比原问题小,然后把它们的答案合起来。这是递推的核心动作。后面第 2、3、4 章会反复看到这个动作,只不过小问题不再是"上月和再上月",而是更细致的拆法。
如果递推式是这种"分最后一步"的产物,那它就是一个对"系统的下一步怎么从这一步长出来"的描述。这一点在后面看动态规划、看矩阵、看组合计数的时候会越来越清楚。
一个更干净的例子:楼梯
兔子问题有人物、有时间、有月份,记起来稍微要绕一下。我们换一个更干净的版本。
你站在一段 n n n 阶楼梯下面。每次你可以走 1 阶或者 2 阶。问走到顶有多少种走法。
我们让 W n W_n W n 表示走 n n n 阶楼梯的方法数。
n = 1 n = 1 n = 1 :只能一步走完,方法数 1。
n = 2 n = 2 n = 2 :可以一步一步走,也可以一步直接跨 2 阶,方法数 2。
n = 3 n = 3 n = 3 :可以 1+1+1、1+2、2+1,方法数 3。
n = 4 n = 4 n = 4 :1+1+1+1、1+1+2、1+2+1、2+1+1、2+2,方法数 5。
得到 1, 2, 3, 5。和兔子数列错位了一下,但形状一样。
为什么会这样?
站在 n n n 阶楼梯下面,你的第一步要么走 1 阶,要么走 2 阶。
如果第一步走 1 阶,那你之后还剩 n − 1 n-1 n − 1 阶要走,剩下的走法数等于 W n − 1 W_{n-1} W n − 1 。
如果第一步走 2 阶,那你之后还剩 n − 2 n-2 n − 2 阶要走,剩下的走法数等于 W n − 2 W_{n-2} W n − 2 。
所以 W n = W n − 1 + W n − 2 W_n=W_{n-1}+W_{n-2} W n = W n − 1 + W n − 2 。这就是斐波那契递推。和兔子问题一模一样。
楼梯的好处是没有兔子,没有月份,没有"上个月再生一对"这种需要画图的事。整个递推就在"第一步走几阶"这一刀里完成。
到这里,斐波那契递推已经在两个看起来不相干的故事里出现了。这件事本身是个暗示:它要说的不是兔子,也不是楼梯,而是"系统的下一步由前两步决定"这种结构本身。
把索引说清楚
斐波那契数列在不同书里的写法不一样。有的从 F 1 = 1 , F 2 = 1 F_1=1,F_2=1 F 1 = 1 , F 2 = 1 开始,第 0 项不存在。有的从 F 0 = 0 , F 1 = 1 F_0=0,F_1=1 F 0 = 0 , F 1 = 1 开始,把第 0 项写出来。
这本书统一用后者:F 0 = 0 , F 1 = 1 F_{0} = 0, F_{1} = 1 F 0 = 0 , F 1 = 1 。也就是说,完整地写出来是 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …。
这样写的好处,到第 7 章矩阵和第 8 章闭式公式那里就会看出来,所有的公式都对得整整齐齐。代价是要记一下:兔子问题和楼梯问题给出的数列,刚好错位一格。
具体来说:
兔子问题里的"第 n 个月兔子对数"对应 F n + 1 F_{n+1} F n + 1 。第 0 0 0 个月是 1 1 1 对,等于 F 1 = 1 F_1=1 F 1 = 1 ;第 1 1 1 个月是 1 1 1 对,等于 F 2 = 1 F_2=1 F 2 = 1 ;第 2 2 2 个月是 2 2 2 对,等于 F 3 = 2 F_3=2 F 3 = 2 ,以此类推。
走 n n n 阶楼梯的方法数 W n W_n W n 对应 F n + 1 F_{n+1} F n + 1 。W 1 = 1 = F 2 W_1=1=F_2 W 1 = 1 = F 2 ,W 2 = 2 = F 3 W_2=2=F_3 W 2 = 2 = F 3 ,W 3 = 3 = F 4 W_3=3=F_4 W 3 = 3 = F 4 ,……
为什么这种错位一直出现?因为"事件从 1 开始数"和"数列从 0 开始数"在描述同一件事时,自然差一格。我们要做的把它说清楚,让小数字充当护栏。
后面所有的组合计数问题,铺砖、不连续子集、图上匹配,我们都会按同样的方式做:先把小数字列出来,再问"对应到 F 的哪一项"。错了就修,不挥手。
再换一个外壳:铺砖
离开兔子,离开楼梯,再看一个完全不同场景里同一个递推。
考虑一条长度为 n n n 的 1 维走廊。你手上有 1 格砖和 2 格砖。问把走廊铺满有多少种铺法。记这个数为 T n T_n T n 。
n = 1 n = 1 n = 1 :只能用 1 块 1 格砖。T 1 = 1 T_1=1 T 1 = 1 。
n = 2 n = 2 n = 2 :可以两块 1 格砖,也可以一块 2 格砖。T 2 = 2 T_2=2 T 2 = 2 。
n = 3 n = 3 n = 3 :1+1+1、1+2、2+1。T 3 = 3 T_3=3 T 3 = 3 。
n = 4 n = 4 n = 4 :1+1+1+1、1+1+2、1+2+1、2+1+1、2+2。T 4 = 5 T_4=5 T 4 = 5 。
1, 2, 3, 5。又是它。
最后一块砖要么是 1 格,要么是 2 格。如果最后一块是 1 格,前面剩下长度 n − 1 n-1 n − 1 的走廊,铺法数等于 T n − 1 T_{n-1} T n − 1 。如果最后一块是 2 格,前面剩下长度 n − 2 n-2 n − 2 的走廊,铺法数等于 T n − 2 T_{n-2} T n − 2 。所以 T n = T n − 1 + T n − 2 T_n=T_{n-1}+T_{n-2} T n = T n − 1 + T n − 2 。
铺砖、楼梯、兔子,三个完全不同的故事,推出来的递推式完全一样。它们都是"系统的下一步由前两步决定"这个结构的不同外衣。
你换衣服,结构不变。
第 10 章会把铺砖问题正式当成组合计数的入口,到时候我们还会再回来仔细数一数,看这个 5 是怎么从五块真实的砖头里数出来的。这里只是先让你看一眼:斐波那契数能被一块一块摆出来。
把它写成一个定义
现在我们有了递推 F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} F n = F n − 1 + F n − 2 和初值 F 0 = 0 , F 1 = 1 F_0=0,F_1=1 F 0 = 0 , F 1 = 1 。这就构成了一个完整的定义。给定任意一个非负整数 n,按这个定义都能算出一个唯一的 F n F_n F n 。
定义只说了"F n F_n F n 等于前两项之和"。它没说"怎么算 F n F_n F n "。
这两件事看起来一样,其实差很远。定义是数学的事,它告诉你 F n F_n F n 是什么。算法是计算的事,它告诉你怎么把这个值拿出来。
把定义直接翻译成算法,最朴素的版本就是:
function F(n):
if n == 0: return 0
if n == 1: return 1
return F(n-1) + F(n-2)
这段程序读起来和数学定义几乎一模一样。它会正确给出 F n F_n F n 。但下一章我们会看到,它也是你写过最慢的程序之一。
斐波那契给我们的第一课来自这里:数学定义和高效算法之间有一道沟。优雅的定义不保证优雅的计算。
这个教训听起来好像只关于斐波那契。其实它出现在很多地方。一个图的最短路可以用递归定义写得很短,但直接跑会爆炸。一个组合数 ( n k ) = ( n − 1 k − 1 ) + ( n − 1 k ) \binom{n}{k}=\binom{n-1}{k-1}+\binom{n-1}{k} ( k n ) = ( k − 1 n − 1 ) + ( k n − 1 ) 看上去人畜无害,直接展开会算到天荒地老。一个概率分布的边缘分布可以用全概率公式写一行,真去算要遍历整个样本空间。
这些场景看起来各不相同,背后都是同一件事:定义回答"是什么",算法回答"怎么算",二者之间常常差一个聪明的中间步骤。
斐波那契的好处是它小到可以看清这个差距是怎么出现的,又大到可以让我们看清楚怎么把差距填上。
这章我们看到了什么
兔子问题、楼梯问题、铺砖问题,三个看起来毫无关系的故事,最后都给了我们同一个递推 F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} F n = F n − 1 + F n − 2 。这件事本身就在告诉我们:递推从"把系统的下一步分成几种情况"这一动作里自然长出来。
我们把索引说清楚了:本书统一用 F 0 = 0 , F 1 = 1 F_0=0,F_1=1 F 0 = 0 , F 1 = 1 ,组合计数问题对应的下标会差一格,错位时用小数字修。
最后我们把递推直接翻译成了一段最朴素的递归程序,并且发现:定义看起来够清楚了,可是它并没有告诉我们"怎么算才不慢"。
第一扇门开了。后面那扇门后面是一段慢得离谱的程序,慢到你会怀疑电脑是不是坏了。
我们去把它拆开看看。
第 2 章 把它翻译成程序:朴素递归
上一章末尾那段递归程序,我们这一章真的拿去跑一下。
function F(n):
if n == 0: return 0
if n == 1: return 1
return F(n-1) + F(n-2)
读起来和数学定义 F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} F n = F n − 1 + F n − 2 几乎一一对应。你看不出它有什么问题。它也没有 bug。它给出的答案永远是对的。
但是你跑 F 10 F_{10} F 10 ,瞬间。F 20 F_{20} F 20 ,瞬间。F 30 F_{30} F 30 ,等 0.1 秒。F 40 F_{40} F 40 ,等几秒。F 45 F_{45} F 45 ,等一分钟。F 50 F_{50} F 50 ,你可以去泡杯茶。F 100 F_{100} F 100 ,你这台机器大概这辈子算不完。
明明每一步都只是加法,慢在哪里。
先跑几个小例子
跑 F 5 F_5 F 5 看看。表面上是 5 次递归调用。结果是 5。一次加法出来。看上去没什么。
跑 F 10 = 55 F_{10}=55 F 10 = 55 。从 10 个递归调用一直到几十次调用,结果是 55,瞬间给出。
跑 F 20 = 6765 F_{20}=6765 F 20 = 6765 。还是瞬间,但仔细看,调用次数已经不是 20 次了,是 21891 次。
跑 F 30 = 832040 F_{30}=832040 F 30 = 832040 。0.1 秒左右。调用次数约 270 万。
跑 F 40 = 102334155 F_{40} = 102334155 F 40 = 102334155 。几秒。总调用数 T 40 = 2 F 41 − 1 = 331160281 T_{40}=2F_{41}-1=331160281 T 40 = 2 F 41 − 1 = 331160281 ,约 3.3 亿次。
跑 F 50 = 12586269025 F_{50} = 12586269025 F 50 = 12586269025 。几分钟到十几分钟。总调用数 T 50 = 2 F 51 − 1 = 40730022147 T_{50}=2F_{51}-1=40730022147 T 50 = 2 F 51 − 1 = 40730022147 ,约 407 亿次。
数字看上去已经不对劲了。F 40 F_{40} F 40 这台机器几秒就给出 1 亿这个量级的数,但调用次数居然达到 3 亿。也就是说,每次调用平均只产生不到一位数的有效计算。这种"调用次数远超结果位数"的现象,是出问题的征兆。
我们这一章不展开分析。先记下这个现象:朴素递归的调用次数随 n 增长极快,比 F n F_n F n 本身增长还快一档。
下一章我们把调用过程画出来,看清楚这些多出来的调用在做什么。
看一眼小例子的展开
虽然这一章不展开调用树,但跑 F 5 F_5 F 5 还是要看一眼的,否则现象就太抽象了。
F 5 F_5 F 5 要算 F 4 F_4 F 4 + F 3 F_3 F 3 。
F 4 F_4 F 4 要算 F 3 F_3 F 3 + F 2 F_2 F 2 。
F 3 F_3 F 3 要算 F 2 F_2 F 2 + F 1 F_1 F 1 。
F 2 F_2 F 2 要算 F 1 F_1 F 1 + F 0 F_0 F 0 。
最底层 F 0 F_0 F 0 和 F 1 F_1 F 1 直接返回 0 和 1,递归开始往回走。但是注意上面那一行:F 3 F_3 F 3 在 F 5 F_5 F 5 的展开里出现了两次,一次是 F 4 F_4 F 4 的右边,一次是 F 5 F_5 F 5 自己的右边。F 2 F_2 F 2 出现了三次。
跑 F 5 F_5 F 5 总共产生 15 次调用。但是真正"新"的计算只有 F 0 F_0 F 0 , F 1 F_1 F 1 , F 2 F_2 F 2 , F 3 F_3 F 3 , F 4 F_4 F 4 , F 5 F_5 F 5 这 6 项。剩下 9 次调用,都是把已经算过的东西再算一遍。
这才 n = 5。换成 n = 10,整个调用过程里 F 2 F_2 F 2 被调用 34 次,F 1 F_1 F 1 被调用 55 次,F 0 F_0 F 0 被调用 34 次。每一项都被反复重算。
到 n = 30,F 2 F_2 F 2 被算了大约 832040 次。一项被算 80 多万次,这就是为什么 0.1 秒才出结果。
到 n = 50,F 2 F_2 F 2 被调用 F 49 = 7778742049 F_{49} = 7778742049 F 49 = 7778742049 次,约 78 亿次。
到这一步可以下一个粗略判断:朴素递归慢,是因为它在反复算同一件事。
慢的根源是结构性的
慢的根源不在加法本身。加法在所有现代 CPU 上都是纳秒级的。即使 F 50 F_{50} F 50 真的要做 407 亿次加法,按每秒 10 亿次算,也就 40 秒。可朴素递归跑 F 50 F_{50} F 50 要十几分钟,差了一个数量级。所以慢的根源不只是"加法次数多"。
慢的真正根源是:朴素递归算了就忘。每次遇到 F k F_{k} F k ,它都从头开始把 F k F_{k} F k 重新算一遍,完全不管刚才是不是已经算过。
这种"算了就忘"是结构性的,不是某个具体实现的偶然缺陷。递归程序按定义展开,定义本身没说"已经算过的就别再算",所以程序也不会这么干。这是数学定义和高效算法之间那道沟的具体形状:定义回答"是什么",算法听成"每次需要就从头算一遍"。
斐波那契给我们的第二课来自这里:优雅的定义翻译成朴素递归,会变成计算灾难。
这不是递归的错。递归只是一种写法,本身不慢。慢的是这种特定递归,它不保存子问题答案。换成另一种递归(带记忆的递归),同一道题可以快几个数量级。
"翻译"这件事
我们再多想一步。把数学定义翻译成程序,听起来像是一个机械的过程:定义里有什么,程序就写什么。递推式 F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} F n = F n − 1 + F n − 2 被直接翻译成 fib(n)=fib(n-1)+fib(n-2),看起来一一对应。
但是这种"一一对应"忽略了几个东西。
第一,定义没说"什么时候算"。数学里 F n F_n F n 是一个抽象的值,它"就是"那个数。程序里 F n F_n F n 是一个动作,它在某个时刻被执行,产生一个结果。这两个 F n F_n F n 看起来一样,性质完全不同。
第二,定义没说"算过的要不要记"。数学里 F 3 F_3 F 3 永远等于 2,无论你查多少次。程序里 F 3 F_3 F 3 是一个调用,每次调用都重新算,结果当然是 2,但算的过程花时间。
第三,定义没说"按什么顺序算"。数学里 F 5 F_5 F 5 = F 4 F_4 F 4 + F 3 F_3 F 3 是一个等式,左右两边同时成立。程序里要先算 F 4 F_4 F 4 再算 F 3 F_3 F 3 ,或者反过来,顺序由实现决定。不同的顺序可能影响效率。
这三个"没说"加起来,意味着从定义到程序之间有相当大的自由度。朴素递归只是其中一种翻译方式,它选择了"按定义顺序算、算完就丢、不记忆"。这种选择看起来最自然,恰恰因为它最自然,所以也最容易踩坑。
其他翻译方式包括:自顶向下带记忆的递归(先按定义展开,但算过的存下来),自底向上迭代(从 F 0 F_0 F 0 、F 1 F_1 F 1 开始往大算,不用递归),分治加速(发现递推里藏着跳步结构,对数时间搞定)。这些方式都对应同一个数学定义,但性能差好几个数量级。
第 3 章我们会展开调用树,把"算了就忘"这件事画清楚。第 4 章给程序装上记忆。第 5、6 章让程序跳步。每一步都是在重新填"定义"和"算法"之间的那道沟。
一个直觉:朴素递归比 F n F_n F n 本身慢一档
虽然这一章不展开复杂度推导,但有一个直觉值得现在就建立。
朴素递归的调用次数 T n T_n T n 满足 T n ≈ 2 F n + 1 T_n\approx2F_{n+1} T n ≈ 2 F n + 1 。也就是说,调用次数和斐波那契数本身同阶,但稍微大一点,差一个 2 倍和一个位移。
这个事实有几个直接的后果:
F n F_n F n 增长得很快(指数级,约 φ n / 5 \varphi^n/\sqrt5 φ n / 5 )。
T n T_n T n 也增长得很快(也是指数级,约 2 φ n + 1 / 5 2\varphi^{n+1}/\sqrt5 2 φ n + 1 / 5 )。
n n n 增加 1,调用次数大约乘以 1.618(黄金比例)。
n n n 增加 10,调用次数大约乘以 122 倍。
n n n 增加 50,调用次数大约乘以 1 0 10 10^{10} 1 0 10 倍。
这就解释了为什么 F 40 F_{40} F 40 几秒,F 50 F_{50} F 50 几分钟,F 60 F_{60} F 60 大概要好几小时。每多 10 项,时间乘以 122。
下一章我们会把这个直觉推导清楚:为什么 T n ≈ 2 F n + 1 T_n\approx2F_{n+1} T n ≈ 2 F n + 1 ?为什么朴素递归的复杂度恰好是斐波那契数本身?这个"递归算斐波那契的代价是另一个斐波那契数"的事实,是这本书最讽刺的瞬间之一。
这章我们看到了什么
朴素递归跑小例子没事,跑大例子就慢得离谱。慢的根源不在加法本身,而在"算了就忘":每次遇到 F k F_{k} F k 都从头算,不管之前算没算过。
我们把"翻译"这件事看了一眼:从数学定义到程序之间有好几个"没说",朴素递归只是其中一种最自然的翻译,恰恰因为它最自然,所以最容易踩坑。
最后留下一个直觉:朴素递归的调用次数和斐波那契数本身同阶,T n ≈ 2 F n + 1 T_n\approx2F_{n+1} T n ≈ 2 F n + 1 。这意味着 n n n 每增加 1,时间乘以 1.618。下一章我们会把这个直觉推导清楚,看清楚那棵调用树到底长什么样。
第 3 章 调用树与"为什么这么慢"
第 2 章那段程序,我们说要回过头看它怎么慢的。这一章把它的调用过程画出来,让慢这件事有具体的形状。
把 F 5 F_5 F 5 的调用展开
跑 F 5 F_5 F 5 的时候,调用过程是这样的:
F 5 F_5 F 5 要 F 4 F_4 F 4 + F 3 F_3 F 3 。
F 4 F_4 F 4 要 F 3 F_3 F 3 + F 2 F_2 F 2 。
F 3 F_3 F 3 要 F 2 F_2 F 2 + F 1 F_1 F 1 。
F 2 F_2 F 2 要 F 1 F_1 F 1 + F 0 F_0 F 0 。
F 1 F_1 F 1 和 F 0 F_0 F 0 直接返回。
把它画成一棵树:
F(5)
/ \
F(4) F(3)
/ \ / \
F(3) F(2) F(2) F(1)
/ \ / \ / \
F(2) F(1) F(1)F(0)F(1)F(0)
/ \
F(1) F(0)
数一下每个节点被算了多少次。
F 1 F_1 F 1 出现 5 次。
F 0 F_0 F 0 出现 3 次。
F 2 F_2 F 2 出现 3 次。
F 3 F_3 F 3 出现 2 次。
整个树总共 15 个节点。但是真正"新"的计算只有 F 0 F_0 F 0 , F 1 F_1 F 1 , F 2 F_2 F 2 , F 3 F_3 F 3 , F 4 F_4 F 4 , F 5 F_5 F 5 这 6 项。剩下 9 次调用,都是把已经算过的东西再算一遍。
这才 n = 5。换成 n = 10 试试。F 9 F_9 F 9 是一棵独立的斐波那契调用树,F 8 F_8 F 8 也是一棵独立的斐波那契调用树。它们之间共享的部分,F 8 F_8 F 8 , F 7 F_7 F 7 , F 6 F_6 F 6 , …,全部被算了两次。
到 n = 30,整棵树大概有 270 万个节点。到 n = 40,约 3.3 亿个。到 n = 50,约 407 亿个。这就是为什么 F 50 F_{50} F 50 跑不动。要做的加法次数太多。
把"太多"算出来
设 T n T_n T n 是计算 F n F_n F n 所需的递归调用次数。从程序结构看:
T 0 = 1 , T 1 = 1 , T n = T n − 1 + T n − 2 + 1 ( n ≥ 2 ) .
T_0=1,\qquad T_1=1,\qquad T_n=T_{n-1}+T_{n-2}+1\quad(n\ge 2).
T 0 = 1 , T 1 = 1 , T n = T n − 1 + T n − 2 + 1 ( n ≥ 2 ) .
这里的 + 1 +1 + 1 是因为 F n F_n F n 自己也算一次调用。
把 T n + 1 T_n+1 T n + 1 记作 U n U_n U n ,那么
U n = T n + 1 = T n − 1 + T n − 2 + 2 = U n − 1 + U n − 2 .
U_n=T_n+1=T_{n-1}+T_{n-2}+2=U_{n-1}+U_{n-2}.
U n = T n + 1 = T n − 1 + T n − 2 + 2 = U n − 1 + U n − 2 .
又有 U 0 = 2 , U 1 = 2 U_0=2,U_1=2 U 0 = 2 , U 1 = 2 。所以 U n U_n U n 满足的就是斐波那契递推,只是初值是 2 , 2 2,2 2 , 2 而不是 0 , 1 0,1 0 , 1 。这意味着
U n = 2 F n + 1 .
U_n=2F_{n+1}.
U n = 2 F n + 1 .
也就是说
T n = 2 F n + 1 − 1.
T_n=2F_{n+1}-1.
T n = 2 F n + 1 − 1.
朴素递归计算 F n F_n F n 的调用次数,差不多是 2 F n + 1 F_{n+1} F n + 1 。
这是一个很讽刺的结果。你写程序去算 F n F_n F n ,结果程序做的工作量正比于 F n + 1 F_{n+1} F n + 1 。你想算的数,恰好就是你要付出的代价。斐波那契数列增长得很快,所以你的程序也慢得很快。
具体一点:
F 10 = 55 F_{10} = 55 F 10 = 55 ,T 10 = 2 F 11 − 1 = 2 ⋅ 89 − 1 = 177 T_{10}=2F_{11}-1=2\cdot89-1=177 T 10 = 2 F 11 − 1 = 2 ⋅ 89 − 1 = 177 次调用。瞬间。
F 20 = 6765 F_{20} = 6765 F 20 = 6765 ,T 20 = 2 F 21 − 1 = 2 ⋅ 10946 − 1 = 21891 T_{20}=2F_{21}-1=2\cdot10946-1=21891 T 20 = 2 F 21 − 1 = 2 ⋅ 10946 − 1 = 21891 次调用。瞬间。
F 30 = 832040 F_{30} = 832040 F 30 = 832040 ,T 30 = 2 F 31 − 1 = 2 ⋅ 1346269 − 1 = 2692537 T_{30}=2F_{31}-1=2\cdot1346269-1=2692537 T 30 = 2 F 31 − 1 = 2 ⋅ 1346269 − 1 = 2692537 ,约 270 万次。0.1 秒左右。
F 40 = 102334155 F_{40} = 102334155 F 40 = 102334155 ,T 40 = 2 F 41 − 1 = 331160281 T_{40}=2F_{41}-1=331160281 T 40 = 2 F 41 − 1 = 331160281 ,约 3.3 亿次。几秒。
F 50 = 12586269025 F_{50} = 12586269025 F 50 = 12586269025 ,T 50 = 2 F 51 − 1 = 40730022147 T_{50}=2F_{51}-1=40730022147 T 50 = 2 F 51 − 1 = 40730022147 ,约 407 亿次。一个半小时起步。
F 100 ≈ 3.5 × 1 0 20 F_{100} \approx 3.5 \times 10^{20} F 100 ≈ 3.5 × 1 0 20 ,T 100 ≈ 1 0 21 T_{100}\approx10^{21} T 100 ≈ 1 0 21 次调用。即使每次调用 1 纳秒,也要 3 万年。
同一个子问题被算了无数次。F 2 F_2 F 2 在算 F 50 F_{50} F 50 时被算了大约 F 49 F_{49} F 49 ≈ 78 亿次。F 3 F_3 F 3 被算了大约 F 48 F_{48} F 48 ≈ 48 亿次。一棵看似简单的二叉树,因为子问题之间完全重叠,节点数指数级膨胀。
调用树的两条对角线
如果你想再看得精确一点,调用树上有两条线特别有讲究。
第一条线:最左路径。从根 F n F_n F n 一路向左走 F n − 1 F_{n-1} F n − 1 , F n − 2 F_{n-2} F n − 2 , …, F 1 F_1 F 1 , F 0 F_0 F 0 。这条线有 n + 1 个节点,对应"从顶向下递归到最底层"的过程。任何递归实现都必须走完这条线,因为你至少要碰到 F 0 F_0 F 0 和 F 1 F_1 F 1 才能开始返回。
第二条线:最右路径。从根 F n F_n F n 一路向右走 F n − 2 F_{n-2} F n − 2 , F n − 4 F_{n-4} F n − 4 , F n − 6 F_{n-6} F n − 6 , …。这条线长度大约 n/2,也是任何递归实现都要走的。
整个调用树的节点数,主要由"在最左路径基础上往右伸展出去的子树"决定。根的右子树是 F n − 2 F_{n-2} F n − 2 ,它本身又是一棵完整的斐波那契调用树。这棵子树的根的右子树是 F n − 4 F_{n-4} F n − 4 ,又是一棵完整的斐波那契调用树。以此类推。
把这些右子树加起来,你会发现总节点数恰好满足斐波那契递推。所以 T n T_n T n ≈ 2 F n + 1 F_{n+1} F n + 1 这件事,从结构上看就是:调用树是一棵"每一层都长出和上一层差不多大子树"的二叉树,子树之间的内容大量重叠,但程序并不识别这种重叠。
调用树的形状本身就是斐波那契
到这里可以下一个稍微深一点的判断。
朴素递归的调用树,结构上和斐波那契数列同构。每个节点 F k F_{k} F k 的子树大小,等于 T k ≈ 2 F k + 1 T_k\approx2F_{k+1} T k ≈ 2 F k + 1 。每个节点的左子树大小约等于 2 F k F_{k} F k ,右子树大小约等于 2 F k − 1 F_{k-1} F k − 1 。两棵子树加起来差不多等于父节点的子树大小。
这件事的几何效果是:朴素递归的调用树长得像一棵"斐波那契分形"。每个子树都是父树的缩小版,缩小比例大约是 1/φ \varphi φ 和 1/φ 2 \varphi^{2} φ 2 。整棵树的节点数指数级膨胀,膨胀速度就是斐波那契数列本身的增长速度。
这是这本书里最讽刺的一个瞬间。你写程序去算斐波那契数,结果程序构造的调用树本身就是斐波那契形状的。你想算的那个数,恰恰就是你程序的代价。
问题不在递归,问题在没记性
到这里你可能会想:递归是不是不能用?
递归只是一种写法,本身不慢。慢的是这段程序"算了就忘"。
你看,F 5 F_5 F 5 在算 F 4 F_4 F 4 的时候已经把 F 3 F_3 F 3 算了一遍。等它回到顶层去算 F 3 F_3 F 3 的时候,它根本不知道自己刚才算过这玩意儿,从头再来一次。
把同一份工作做了两遍,又两遍,又两遍。整个调用树的膨胀不因为这个程序写得多复杂,而是因为最简单的子问题,F 2 F_2 F 2 , F 3 F_3 F 3 , F 4 F_4 F 4 ,被反复重算。
打个比方。你去超市买东西,列了一张单子:牛奶、面包、鸡蛋、苹果、香蕉。买完回家发现忘了牛奶。再去一次。又买完回家发现还忘了面包。再去一次。如果每次只解决一个遗忘项,你跑超市的次数等于商品数。
朴素递归就是这样。它每遇到一次 F 2 F_2 F 2 就重新算一遍,每次都从最底下 F 0 F_0 F 0 和 F 1 F_1 F 1 一路加起来。它没有一张"已经算过"的备忘录,所以每次都从头来。
让程序长出一张备忘录,是下一章要做的事。这一章我们先把"为什么这么慢"这件事彻底看清楚。
一个更直观的失败
把斐波那契递归展开成调用树,能看出它哪里慢。但这件事其实可以用一个更直观的方式感受。
你跑 F 50 F_{50} F 50 在你的笔记本上要等几分钟。你的笔记本每秒能做几十亿次基本运算。也就是说,在这几分钟里,你的电脑做了 1 0 11 10^{11} 1 0 11 次以上的加法。而 F 50 F_{50} F 50 这个数本身,按定义只需要 50 次加法就能算出来。
50 步加法,几纳秒就完事。可是朴素递归花了几分钟,做了 1 0 11 10^{11} 1 0 11 次运算。中间差的 1 0 10 10^{10} 1 0 10 倍,全是浪费。
这就是定义和算法之间那道沟的具体形状。定义说"F n F_n F n 等于 F n − 1 F_{n-1} F n − 1 + F n − 2 F_{n-2} F n − 2 ",算法听成"每次需要 F n F_n F n ,就把 F n − 1 F_{n-1} F n − 1 和 F n − 2 F_{n-2} F n − 2 都从头算一遍"。这中间没有任何"已经算过就别再算"的意识。
朴素递归把数学定义的优雅翻译成了计算灾难。这是递归的特定写法的错,不是递归本身的错。
这本书第一次出现的反常识
到这里我们可以把这一章的核心说出来。
我们通常觉得,一段程序越简洁、越接近数学定义,它就越优雅,越值得写。这一章里,斐波那契递归就是这种优雅的化身:6 行代码,结构清楚,读起来像诗。
但是优雅的定义不保证优雅的计算。这段最像数学定义的程序,跑起来却极慢。
这件事在算法课里被叫做"定义 ≠ \neq = 算法"。听起来朴素,但是真碰到的时候很容易栽。斐波那契递归是栽这个跟头最好的地方,因为它小到能让你看清跟头是怎么栽的,又大到能让你感觉到疼。
后面第 4 章会讲怎么修,给程序加上一张备忘录,让它从指数级变线性。第 5 章会讲怎么修得更狠,把步子折半,做到对数级。第 6 章会把整个递推改写成矩阵快速幂。每一步都是在重新填"定义"和"算法"之间的那道沟。
但是这一章我们停下来看一下:原来这道沟,是真的存在的。即使是这么简单的一段递归,也会有。
一个值得记住的画面
最后留一个画面。
那棵调用树。每个节点上写着一个 F k F_{k} F k 。整棵树有 2 F n + 1 F_{n+1} F n + 1 − 1 个节点,但你真正需要的"新计算"只有 n + 1 个:F 0 F_0 F 0 , F 1 F_1 F 1 , F 2 F_2 F 2 , …, F n F_n F n 。
剩下所有的节点,都是把已经算过的东西重算一遍。
那些重算的节点,是浪费。它们也是机会。只要把每个 F k F_{k} F k 算过一次之后存下来,下次再遇到就直接查表,所有的重算节点会瞬间消失。一棵 2 F n + 1 F_{n+1} F n + 1 节点的树,会塌缩成 n + 1 个节点的链。
下一章我们就让这棵树塌缩。
第 4 章 给程序装上记忆:动态规划的内核
第 3 章那棵膨胀的调用树,我们说它"算了就忘"。这一章给程序装上一张备忘录,让算过的东西别再算第二遍。整个改动很小,但效果是数量级的。
第一步:把算过的存下来
最直接的修改,是给 F k F_{k} F k 配一张表。每次算 F k F_{k} F k 之前先查表,如果表里已经有,直接拿来用。如果还没有,算一遍,把结果写进表里,再返回。
代码大概长这样:
memo = {0: 0, 1: 1}
function F(n):
if n in memo:
return memo[n]
result = F(n-1) + F(n-2)
memo[n] = result
return result
加了两行代码,效果完全不一样。
跑 F 50 F_{50} F 50 :原来要算约 407 亿次,现在算 51 次(F 0 F_0 F 0 到 F 50 F_{50} F 50 各一次)。跑 F 100 F_{100} F 100 :原来要算 1 0 21 10^{21} 1 0 21 次,现在 101 次。
为什么差距这么大?因为每个 F k F_{k} F k 在整张调用树里只第一次被算。之后所有遇到 F k F_{k} F k 的地方,都查表返回,不再往下展开。
回到第 3 章那棵 F 5 F_5 F 5 的调用树:
F(5)
/ \
F(4) F(3)
/ \ / \
F(3) F(2) F(2) F(1)
/ \ / \ / \
F(2) F(1) F(1)F(0)F(1)F(0)
/ \
F(1) F(0)
加了备忘录之后,调用过程变成:
F 5 F_5 F 5 没在表里,要算 F 4 F_4 F 4 + F 3 F_3 F 3 。
F 4 F_4 F 4 没在表里,要算 F 3 F_3 F 3 + F 2 F_2 F 2 。
F 3 F_3 F 3 没在表里,要算 F 2 F_2 F 2 + F 1 F_1 F 1 。
F 2 F_2 F 2 没在表里,要算 F 1 F_1 F 1 + F 0 F_0 F 0 。两个都在表里,返回 1 + 0 = 1。写入 memo[2] = 1。
F 1 F_1 F 1 在表里,返回 1。
F 3 F_3 F 3 算完:1 + 1 = 2 1 + 1 = 2 1 + 1 = 2 。写入 memo[3] = 2。
F 2 F_2 F 2 在表里,返回 1。
F 4 F_4 F 4 算完:2 + 1 = 3 2 + 1 = 3 2 + 1 = 3 。写入 memo[4] = 3。
F 3 F_3 F 3 在表里,返回 2。
F 5 F_5 F 5 算完:3 + 2 = 5 3 + 2 = 5 3 + 2 = 5 。写入 memo[5] = 5。
10 步。原来 15 步。看上去差距不大。但放大到 n = 50,原来是 407 亿步,现在是 51 步。
那棵原来指数膨胀的树,被一张备忘录压扁成一条链。
这种写法有个名字:记忆化
把"算过的就存下来"这个动作加到递归上,叫做记忆化(memoization)。这个英文字生造于 1968 年,是 Donald Michie 提出来的,意思是"把函数已经算过的结果记下来,下次再问就直接给答案"。
记忆化是一个很通用的技巧,不限于斐波那契。任何递归函数,只要满足两条性质,都可以记忆化加速:
函数对同一个输入永远给出同一个输出(没有副作用、没有外部状态)。
在递归展开的过程中,同一个输入会被反复问到。
斐波那契递归满足这两条。所以记忆化一上去,2 F n + 1 F_{n+1} F n + 1 次调用塌缩成 n + 1 次。
记忆化的好处是改动小。原本那段优雅的递归代码不动,只在外面包一层"先查表"的逻辑,整体结构没变。
代价是要花内存。memo 表里要存 n + 1 个数。对斐波那契来说,n 通常不会太大,几十兆就够算到 F 1 0 6 F_{10^6} F 1 0 6 ,所以这点内存完全可以接受。
第二步:换个方向,自底向上
记忆化是"自顶向下"的:从 F n F_n F n 出发,递归到 F 0 F_0 F 0 和 F 1 F_1 F 1 ,然后一层层算回来。
可以反过来,"自底向上":从 F 0 F_0 F 0 和 F 1 F_1 F 1 出发,一步步往上算到 F n F_n F n 。
function F(n):
if n == 0: return 0
if n == 1: return 1
a, b = 0, 1
for i = 2 to n:
a, b = b, a + b
return b
跑一下 n = 5 看看。
初始:a = 0 , b = 1 a = 0, b = 1 a = 0 , b = 1 (这就是 F 0 F_0 F 0 和 F 1 F_1 F 1 )。
i = 2 i = 2 i = 2 :a , b = 1 , 0 + 1 = 1 a, b = 1, 0+1 = 1 a , b = 1 , 0 + 1 = 1 (这就是 F 1 F_1 F 1 , F 2 F_2 F 2 )。
i = 3 i = 3 i = 3 :a , b = 1 , 1 + 1 = 2 a, b = 1, 1+1 = 2 a , b = 1 , 1 + 1 = 2 (这就是 F 2 F_2 F 2 , F 3 F_3 F 3 )。
i = 4 i = 4 i = 4 :a , b = 2 , 1 + 2 = 3 a, b = 2, 1+2 = 3 a , b = 2 , 1 + 2 = 3 (这就是 F 3 F_3 F 3 , F 4 F_4 F 4 )。
i = 5 i = 5 i = 5 :a , b = 3 , 2 + 3 = 5 a, b = 3, 2+3 = 5 a , b = 3 , 2 + 3 = 5 (这就是 F 4 F_4 F 4 , F 5 F_5 F 5 )。
返回 b = 5。
5 次循环,5 次加法,得到 F 5 F_5 F 5 。换成 n = 50,50 次循环。换成 n = 1000,1000 次循环。
时间和记忆化一样是 O ( n ) O(n) O ( n ) ,但是空间省了:自底向上版本只用两个变量 a 和 b,O ( 1 ) O(1) O ( 1 ) 空间。记忆化要用 O ( n ) O(n) O ( n ) 空间存整张表。
为什么自底向上能省空间?因为它不需要"回头"。F k F_{k} F k 一旦用来算 F k + 1 F_{k+1} F k + 1 之后就再也不需要了。除了 F k + 1 F_{k+1} F k + 1 计算时用到的两个值 F k F_{k} F k 和 F k − 1 F_{k-1} F k − 1 ,其他都不用记。所以只需要两个变量来回倒腾就行。
自顶向下的记忆化不一样,它要把整张表留在那里,因为递归回去的时候可能还要查。其实严格来说,记忆化版本如果改成"只保留最近两项"也是可以的,但写起来没有自底向上那么自然。
这两个写法,本质上做的是同一件事
记忆化和自底向上迭代,从代码看是完全不同的两套写法。一个递归,一个循环。一个用字典,一个用两个变量。
但是从"算了几次加法"这个角度看,它们完全一样:都是 n − 1 次加法,每个 F k F_{k} F k 算一次,从 k = 2 算到 k = n。
它们都在做同一件事:让每个子问题只被解决一次,然后被复用。
这件事有一个统一的名字:动态规划。
动态规划到底是什么
动态规划这个名字,是 1950 年代 Richard Bellman 起的。这个名字起得不太好,"dynamic" 和 "programming" 都和它实际做的事没什么直接关系。Bellman 自己在回忆录里说,起这个名字主要是为了让资助方听上去觉得时髦,本质上是公关考虑。
抛开名字不谈,动态规划的核心其实就一句话:
如果一个问题可以拆成若干个重叠的子问题,那么把每个子问题的答案记下来,下次再用就直接拿,别再算第二遍。
就这么一句。
这句话里有两个关键词。"重叠"和"记下来"。
"重叠"指的是不同子问题之间会有公共部分。斐波那契递归里 F 50 F_{50} F 50 需要 F 49 F_{49} F 49 和 F 48 F_{48} F 48 ,F 49 F_{49} F 49 又需要 F 48 F_{48} F 48 和 F 47 F_{47} F 47 ,F 48 F_{48} F 48 是两个父问题的公共子问题。重叠是动态规划起作用的前提。如果子问题完全不重叠(比如归并排序里的两个一半),那每个子问题只被解决一次,根本不需要记忆化,分治就够了。
"记下来"指的是用一张表(或者两个变量、一个数组、一个字典)保存子问题的答案。这是动态规划和朴素递归的区别。朴素递归也算子问题,但算完就丢,所以同一个子问题被反复解决。动态规划算完不丢,所以每个子问题只算一次。
斐波那契是动态规划最简单的样本。它只有一个一维的状态(k 从 0 到 n),状态转移也很简单(F k = F k − 1 + F k − 2 F_{k} = F_{k-1} + F_{k-2} F k = F k − 1 + F k − 2 )。但它已经把动态规划的两条核心都展示出来了:有重叠子问题,且通过记忆化或自底向上迭代把每个子问题算一次。
动态规划不是背模板
很多人学动态规划的方式,是背一堆模板:背包问题、最长公共子序列、最长上升子序列、编辑距离、矩阵链乘法。每个模板一个转移方程、一组边界条件,套上去就能做。
这种学法的问题在于:它把动态规划当成一堆孤立的技巧,掩盖了它真正的内核。
动态规划的内核其实很简单:识别子问题,发现它们重叠,决定先算哪个,然后算一次就够了。
模板只是这个内核在不同问题上的具体应用。背模板而不懂内核,会做的是模板题。懂了内核,模板只是顺手的事。
我们用斐波那契把内核拆开来看:
识别子问题。原问题是"算 F n F_n F n "。子问题是"算 F k F_{k} F k ,其中 0 ≤ \leq ≤ k ≤ \leq ≤ n"。
发现重叠。F n F_n F n 依赖 F n − 1 F_{n-1} F n − 1 和 F n − 2 F_{n-2} F n − 2 ,F n − 1 F_{n-1} F n − 1 又依赖 F n − 2 F_{n-2} F n − 2 和 F n − 3 F_{n-3} F n − 3 ,所以 F n − 2 F_{n-2} F n − 2 被两个父问题需要。重叠出现了。
决定顺序。从 k = 0 开始往大算,到 k = n 结束。每个 F k F_{k} F k 只在 F k − 1 F_{k-1} F k − 1 和 F k − 2 F_{k-2} F k − 2 都已知时才被算。
算一次。每个 F k F_{k} F k 在被算出来之后存起来(或丢弃,如果不再需要),下次再用就直接拿。
这四步就是动态规划的全部。模板题里那些复杂的状态定义、奇怪的转移方程,都是这四步在具体问题上的细化。
把动态规划的第一层意思说清楚
到这里可以做一个清楚的判断:
动态规划的核心是一种关于"计算有记忆"的意识。
朴素递归的意识是:每次需要什么就算什么,算完丢掉。
动态规划的意识是:已经算过的东西,不该再付一次代价。
这两种意识之间,差的不只是一张表。差的是对"什么是计算"的根本理解。
朴素递归把计算理解成"按定义展开"。这种理解在数学上是对的,但在工程上不对。展开会出现大量重复,重复会爆炸。
动态规划把计算理解成"按依赖图遍历"。每个子问题是一个节点,依赖关系是边,按拓扑序遍历这张图,每个节点只访问一次。
这两种理解给出的算法,时间复杂度可以差指数级。斐波那契是最干净的例子:朴素递归 Θ ( φ n ) \Theta(\varphi^n) Θ ( φ n ) ,动态规划 Θ ( n ) \Theta(n) Θ ( n ) 。
动态规划的代价
动态规划不是免费的。它用时间换时间,用空间换时间。
空间上,自顶向下要 O ( n ) O(n) O ( n ) 的备忘录,自底向上虽然能压到 O ( 1 ) O(1) O ( 1 ) ,但本质上还是"在某个时刻需要保存某些状态"。在更复杂的问题里,状态空间可能很大。背包问题 O ( n W ) O(nW) O ( nW ) ,矩阵链乘法 O ( n 2 ) O(n^2) O ( n 2 ) ,编辑距离 O ( m n ) O(mn) O ( mn ) 。空间常常成为瓶颈。
时间上,虽然每个子问题只算一次,但是子问题的总数本身可能很多。某些问题(比如旅行商)子问题数是指数级的,动态规划只能把"指数的指数"降到"指数",依然不可解。
所以动态规划只在子问题数量可控、且子问题之间确实重叠的时候有用。
对斐波那契来说,子问题数量是 O ( n ) O(n) O ( n ) ,重叠是显然的,所以动态规划是最合适的工具。从 Θ ( φ n ) \Theta(\varphi^n) Θ ( φ n ) 降到 Θ ( n ) \Theta(n) Θ ( n ) ,这一步已经把朴素递归慢成灾难的问题解决了。
还能更快吗
O ( n ) O(n) O ( n ) 听起来已经很快了。F 1 0 6 F_{10^6} F 1 0 6 用自底向上迭代跑,几毫秒就完事。F 1 0 9 F_{10^9} F 1 0 9 跑不动了。这不能怪算法,是数本身太大,存不下来。
但是从算法角度,O ( n ) O(n) O ( n ) 还能更快。下一章会讲怎么把步子折半,做到 O ( log n ) O(\log n) O ( log n ) 。
那里的关键,是发现斐波那契递推里藏着一种"翻倍"的结构。F n + m F_{n+m} F n + m 可以从 F n F_n F n , F m F_{m} F m 和它们附近的几个值直接算出来,不需要从 F 0 F_0 F 0 一路加到 F n + m F_{n+m} F n + m 。这等于说,从 F k F_{k} F k 跳到 F 2 k F_{2k} F 2 k 不需要 k 步加法,只需要常数次乘法和加法。
但是先停下来想一想:从 O ( φ n ) O(\varphi^n) O ( φ n ) 到 O ( n ) O(n) O ( n ) 这一步,已经够我们记一辈子了。它告诉我们一件事:
优雅的定义不保证优雅的计算。给计算加上一张备忘录,常常就能把优雅找回来。
斐波那契数列给我们的第三课就是这个:计算是有记忆的,算过的东西不该再付一次代价。
这件事听起来朴素,但它就是动态规划的全部。
下一章我们问:除了"算过的就别再算",还能不能再省一些?答案藏在递推自身的结构里。
第 5 章 快速倍增:从加法到乘法
到上一章为止,我们最好的算法是 O ( n ) O(n) O ( n ) 。要算 F n F_n F n ,从 F 0 F_0 F 0 走到 F n F_n F n ,每一步一次加法,n 步搞定。
这一章问的是:能不能不一步一步走?能不能跳?
答案是可以。斐波那契递推里藏着一种"翻倍"的结构。从 F k F_{k} F k 跳到 F 2 k F_{2k} F 2 k ,不需要 k 步加法,只需要常数次乘法和加法。这一步把 O ( n ) O(n) O ( n ) 砍成 O ( log n ) O(\log n) O ( log n ) 。
先想清楚"翻倍"是什么意思
假设我们已经知道 F k F_{k} F k 和 F k + 1 F_{k+1} F k + 1 。能不能从这两个数直接算出 F 2 k F_{2k} F 2 k 和 F 2 k + 1 F_{2k+1} F 2 k + 1 ,而不需要从 F k + 2 F_{k+2} F k + 2 一路加到 F 2 k F_{2k} F 2 k ?
如果能,那从 F 0 F_0 F 0 , F 1 F_1 F 1 出发,反复翻倍,就能跳着算到任意 F n F_n F n 。
知道 F 0 F_0 F 0 , F 1 F_1 F 1 ,翻一次倍,得到 F 0 F_0 F 0 , F 1 F_1 F 1 , F 2 F_2 F 2 , F 3 F_3 F 3 里的 F 2 F_2 F 2 和 F 3 F_3 F 3 。
再翻一次倍,从 F 2 F_2 F 2 , F 3 F_3 F 3 得到 F 4 F_4 F 4 , F 5 F_5 F 5 。
再翻一次倍,从 F 4 F_4 F 4 , F 5 F_5 F 5 得到 F 8 F_8 F 8 , F 9 F_9 F 9 。
再翻一次倍,从 F 8 F_8 F 8 , F 9 F_9 F 9 得到 F 16 F_{16} F 16 , F 17 F_{17} F 17 。
每一次翻倍,下标翻一倍。所以从 F 0 F_0 F 0 , F 1 F_1 F 1 走到 F n F_n F n ,只需要 log 2 n \log_2 n log 2 n 次翻倍。
这个思路要工作,关键是有一个公式把 F 2 k F_{2k} F 2 k 和 F 2 k + 1 F_{2k+1} F 2 k + 1 用 F k F_{k} F k 和 F k + 1 F_{k+1} F k + 1 直接写出来。我们下面就把它推出来。
推加法公式
要推翻倍公式,先要推一个更基础的公式:加法公式。它说 F n + m F_{n+m} F n + m 可以用 F n F_n F n 和 F m F_{m} F m 附近的几个值表示出来。
我们先猜 F n + m F_{n+m} F n + m 是 F n F_n F n 和 F m F_{m} F m 的某种线性组合。也就是说,F n + m = a F n + 1 + b F n + c F m + 1 + d F m F_{n+m} = a F_{n+1} + b F_{n} + c F_{m+1} + d F_{m} F n + m = a F n + 1 + b F n + c F m + 1 + d F m 这种形式,其中 a , b , c , d a,b,c,d a , b , c , d 是常数。
但是这种猜法太宽。我们直接做。
考虑 F n + m F_{n+m} F n + m 。固定 m m m ,把 n n n 从 0 0 0 开始一个个推:
F m = F m F_{m} = F_{m} F m = F m 。这是 n = 0 n=0 n = 0 。
F m + 1 = F m + 1 F_{m+1} = F_{m+1} F m + 1 = F m + 1 。这是 n = 1 n=1 n = 1 。
F m + 2 = F m + 1 + F m F_{m+2} = F_{m+1} + F_{m} F m + 2 = F m + 1 + F m 。这是 n = 2 n=2 n = 2 。
F m + 3 = F m + 2 + F m + 1 = 2 F m + 1 + F m F_{m+3} = F_{m+2} + F_{m+1} = 2 F_{m+1} + F_{m} F m + 3 = F m + 2 + F m + 1 = 2 F m + 1 + F m 。
F m + 4 = F m + 3 + F m + 2 = 3 F m + 1 + 2 F m F_{m+4} = F_{m+3} + F_{m+2} = 3 F_{m+1} + 2 F_{m} F m + 4 = F m + 3 + F m + 2 = 3 F m + 1 + 2 F m 。
F m + 5 = F m + 4 + F m + 3 = 5 F m + 1 + 3 F m F_{m+5} = F_{m+4} + F_{m+3} = 5 F_{m+1} + 3 F_{m} F m + 5 = F m + 4 + F m + 3 = 5 F m + 1 + 3 F m 。
看出来了吗?当 n ≥ 1 n\ge1 n ≥ 1 时,
F m + n = F n F m + 1 + F n − 1 F m .
F_{m+n}=F_nF_{m+1}+F_{n-1}F_m.
F m + n = F n F m + 1 + F n − 1 F m .
这里先把边界说清楚:这个写法默认 n ≥ 1 n\ge1 n ≥ 1 ,避免引入 F − 1 F_{-1} F − 1 。如果 n = 0 n=0 n = 0 ,公式左边就是 F m F_m F m ,直接单独验证即可。
验证一下。n = 5 , m = 2 n = 5, m = 2 n = 5 , m = 2 :F 7 = F 5 F 3 + F 4 F 2 = 5 ⋅ 2 + 3 ⋅ 1 = 13 F_{7} = F_{5} F_{3} + F_{4} F_{2} = 5 \cdot 2 + 3 \cdot 1 = 13 F 7 = F 5 F 3 + F 4 F 2 = 5 ⋅ 2 + 3 ⋅ 1 = 13 。对,F 7 = 13 F_{7} = 13 F 7 = 13 。
n = 4 , m = 3 n = 4, m = 3 n = 4 , m = 3 :F 7 = F 4 F 4 + F 3 F 3 = 3 ⋅ 3 + 2 ⋅ 2 = 13 F_{7} = F_{4} F_{4} + F_{3} F_{3} = 3 \cdot 3 + 2 \cdot 2 = 13 F 7 = F 4 F 4 + F 3 F 3 = 3 ⋅ 3 + 2 ⋅ 2 = 13 。也对。
把下标对称地换一种写法,常用的加法公式是:
F n + m = F n + 1 F m + F n F m − 1 .
F_{n+m}=F_{n+1}F_m+F_nF_{m-1}.
F n + m = F n + 1 F m + F n F m − 1 .
这一式默认 n ≥ 0 , m ≥ 1 n\ge0,m\ge1 n ≥ 0 , m ≥ 1 。如果想让 m = 0 m=0 m = 0 也包括进来,就需要额外约定负指标,或者把 m = 0 m=0 m = 0 作为边界情形单独验证。这个公式可以数学归纳法证明(对 n n n 归纳)。第 14 章会给出它的组合证明。重要的是它给了我们一个工具:F n + m F_{n+m} F n + m 可以从 F n F_n F n , F n + 1 F_{n+1} F n + 1 , F m F_{m} F m , F m − 1 F_{m-1} F m − 1 这四个值直接算出来,不需要从 F 0 F_0 F 0 一路加到 F n + m F_{n+m} F n + m 。
推翻倍公式
现在令 n = m = k n=m=k n = m = k ,加法公式给出:
F 2 k = F k + 1 F k + F k F k − 1 = F k [ F k + 1 + F k − 1 ] F_{2k} = F_{k+1} F_{k} + F_{k} F_{k-1} = F_{k} [F_{k+1} + F_{k-1}] F 2 k = F k + 1 F k + F k F k − 1 = F k [ F k + 1 + F k − 1 ]
但是
F k + 1 + F k − 1 = F k + 1 + ( F k + 1 − F k ) = 2 F k + 1 − F k .
F_{k+1}+F_{k-1}=F_{k+1}+(F_{k+1}-F_k)=2F_{k+1}-F_k.
F k + 1 + F k − 1 = F k + 1 + ( F k + 1 − F k ) = 2 F k + 1 − F k .
最后一个等号用了
F k − 1 = F k + 1 − F k ,
F_{k-1}=F_{k+1}-F_k,
F k − 1 = F k + 1 − F k ,
它可以从 F k + 1 = F k + F k − 1 F_{k+1}=F_k+F_{k-1} F k + 1 = F k + F k − 1 推出。
所以:
F 2 k = F k ⋅ ( 2 F k + 1 − F k ) F_{2k}=F_k\cdot(2F_{k+1}-F_k) F 2 k = F k ⋅ ( 2 F k + 1 − F k )
这就是 F 2 k F_{2k} F 2 k 的翻倍公式。
类似地,令 n = k + 1 , m = k n=k+1,m=k n = k + 1 , m = k :
F 2 k + 1 = F k + 2 F k + F k + 1 F k − 1 .
F_{2k+1}=F_{k+2}F_k+F_{k+1}F_{k-1}.
F 2 k + 1 = F k + 2 F k + F k + 1 F k − 1 .
用
F k + 2 = F k + 1 + F k , F k − 1 = F k + 1 − F k ,
F_{k+2}=F_{k+1}+F_k,
\qquad
F_{k-1}=F_{k+1}-F_k,
F k + 2 = F k + 1 + F k , F k − 1 = F k + 1 − F k ,
得到
F 2 k + 1 = ( F k + 1 + F k ) F k + F k + 1 ( F k + 1 − F k ) = F k + 1 F k + F k 2 + F k + 1 2 − F k + 1 F k = F k 2 + F k + 1 2 .
\begin{aligned}
F_{2k+1}
&=(F_{k+1}+F_k)F_k+F_{k+1}(F_{k+1}-F_k)\\
&=F_{k+1}F_k+F_k^2+F_{k+1}^2-F_{k+1}F_k\\
&=F_k^2+F_{k+1}^2.
\end{aligned}
F 2 k + 1 = ( F k + 1 + F k ) F k + F k + 1 ( F k + 1 − F k ) = F k + 1 F k + F k 2 + F k + 1 2 − F k + 1 F k = F k 2 + F k + 1 2 .
所以:
F 2 k + 1 = F k 2 + F k + 1 2 .
F_{2k+1}=F_k^2+F_{k+1}^2.
F 2 k + 1 = F k 2 + F k + 1 2 .
把两个公式写在一起:
F 2 k = F k ( 2 F k + 1 − F k ) ,
F_{2k}=F_k(2F_{k+1}-F_k),
F 2 k = F k ( 2 F k + 1 − F k ) ,
F 2 k + 1 = F k 2 + F k + 1 2 .
F_{2k+1}=F_k^2+F_{k+1}^2.
F 2 k + 1 = F k 2 + F k + 1 2 .
这两个公式合起来叫"快速倍增公式"。
验证一下
拿 k = 3 k=3 k = 3 试一下。F 3 = 2 , F 4 = 3 F_3=2,F_4=3 F 3 = 2 , F 4 = 3 。
按公式:
F 6 = F 3 ⋅ ( 2 F 4 − F 3 ) = 2 ⋅ ( 6 − 2 ) = 8 F_6=F_3\cdot(2F_4-F_3)=2\cdot(6-2)=8 F 6 = F 3 ⋅ ( 2 F 4 − F 3 ) = 2 ⋅ ( 6 − 2 ) = 8
F 7 = F 3 2 + F 4 2 = 4 + 9 = 13 F_7=F_3^2+F_4^2=4+9=13 F 7 = F 3 2 + F 4 2 = 4 + 9 = 13
实际值:F 6 = 8 , F 7 = 13 F_{6} = 8, F_{7} = 13 F 6 = 8 , F 7 = 13 。对。
再拿 k = 5 k=5 k = 5 试一下。F 5 = 5 , F 6 = 8 F_5=5,F_6=8 F 5 = 5 , F 6 = 8 。
F 10 = F 5 ⋅ ( 2 F 6 − F 5 ) = 5 ⋅ ( 16 − 5 ) = 55 F_{10}=F_5\cdot(2F_6-F_5)=5\cdot(16-5)=55 F 10 = F 5 ⋅ ( 2 F 6 − F 5 ) = 5 ⋅ ( 16 − 5 ) = 55
F 11 = F 5 2 + F 6 2 = 25 + 64 = 89 F_{11}=F_5^2+F_6^2=25+64=89 F 11 = F 5 2 + F 6 2 = 25 + 64 = 89
实际值:F 10 = 55 , F 11 = 89 F_{10} = 55, F_{11} = 89 F 10 = 55 , F 11 = 89 。对。
公式稳。
把它写成算法
用这两个公式,可以从 ( F 0 , F 1 ) = ( 0 , 1 ) (F_0,F_1)=(0,1) ( F 0 , F 1 ) = ( 0 , 1 ) 出发,按 n n n 的二进制位一步步跳到 ( F n , F n + 1 ) (F_n,F_{n+1}) ( F n , F n + 1 ) 。
具体怎么跳?我们让「当前知道的」始终是一对 ( F k , F k + 1 ) (F_k,F_{k+1}) ( F k , F k + 1 ) 。从 ( F 0 , F 1 ) (F_0,F_1) ( F 0 , F 1 ) 开始,每一步可以选择:
翻倍:把 ( F k , F k + 1 ) (F_k,F_{k+1}) ( F k , F k + 1 ) 变成 ( F 2 k , F 2 k + 1 ) (F_{2k},F_{2k+1}) ( F 2 k , F 2 k + 1 ) 。用上面两个公式。
加一:把 ( F k , F k + 1 ) (F_k,F_{k+1}) ( F k , F k + 1 ) 变成 ( F k + 1 , F k + 2 ) (F_{k+1},F_{k+2}) ( F k + 1 , F k + 2 ) 。其中 F k + 2 = F k + F k + 1 F_{k+2}=F_k+F_{k+1} F k + 2 = F k + F k + 1 。
要从 k = 0 k=0 k = 0 跳到任意 n n n ,按 n n n 的二进制位从高到低走:每一步先翻倍,如果当前二进制位是 1 就再翻加一。这和快速幂的思路完全一样。
举个例子,n = 13 = 110 1 2 n=13=1101_2 n = 13 = 110 1 2 。从 ( F 0 , F 1 ) = ( 0 , 1 ) (F_0,F_1)=(0,1) ( F 0 , F 1 ) = ( 0 , 1 ) 出发:
最高位 1 1 1 :先翻倍(到 k = 0 × 2 = 0 k=0\times 2=0 k = 0 × 2 = 0 ),再加一(到 k = 1 k=1 k = 1 )。得到 ( F 1 , F 2 ) = ( 1 , 1 ) (F_1,F_2)=(1,1) ( F 1 , F 2 ) = ( 1 , 1 ) 。
下一位 1 1 1 :先翻倍(到 k = 2 k=2 k = 2 ),再加一(到 k = 3 k=3 k = 3 )。得到 ( F 3 , F 4 ) = ( 2 , 3 ) (F_3,F_4)=(2,3) ( F 3 , F 4 ) = ( 2 , 3 ) 。
下一位 0 0 0 :先翻倍(到 k = 6 k=6 k = 6 ),不加一。得到 ( F 6 , F 7 ) = ( 8 , 13 ) (F_6,F_7)=(8,13) ( F 6 , F 7 ) = ( 8 , 13 ) 。
下一位 1 1 1 :先翻倍(到 k = 12 k=12 k = 12 ),再加一(到 k = 13 k=13 k = 13 )。得到 ( F 13 , F 14 ) = ( 233 , 377 ) (F_{13},F_{14})=(233,377) ( F 13 , F 14 ) = ( 233 , 377 ) 。
四步,从 (F 0 F_0 F 0 , F 1 F_1 F 1 ) 跳到 (F 13 F_{13} F 13 , F 14 F_{14} F 14 )。每步常数次乘法和加法。如果用朴素自底向上,要 13 步。
对一般的 n n n ,二进制位数是 log 2 n \log_2 n log 2 n ,所以总共 O ( log n ) O(\log n) O ( log n ) 步。每步常数次大数运算,每次大数运算的时间取决于数的大小,但总时间仍然是 O ( log n ) O(\log n) O ( log n ) 次乘法的代价。
这就是快速倍增
这个算法叫"快速倍增"(fast doubling)。它的核心是:
一对相邻斐波那契数 (F k F_{k} F k , F k + 1 F_{k+1} F k + 1 ) 可以被翻倍成 (F 2 k F_{2k} F 2 k , F 2 k + 1 F_{2k+1} F 2 k + 1 )。
用 n n n 的二进制表示控制翻倍和加一,跳着到 (F n F_n F n , F n + 1 F_{n+1} F n + 1 )。
时间 O ( log n ) O(\log n) O ( log n ) ,空间 O ( 1 ) O(1) O ( 1 ) (不算大数存储)。
跑 F 1 0 18 F_{10^{18}} F 1 0 18 ,自底向上要 1 0 18 10^{18} 1 0 18 步,几亿年。快速倍增在下标层面的递归深度约为 log 2 ( 1 0 18 ) ≈ 60 \log_2(10^{18}) \approx 60 log 2 ( 1 0 18 ) ≈ 60 步。
但要小心一个细节。如果只算 F 1 0 18 m o d m F_{10^{18}} \bmod m F 1 0 18 mod m 这种余数,60 步确实足够,因为中间结果不超过 m 2 m^2 m 2 。如果要输出精确整数,结果本身约有 2.09 × 1 0 17 2.09 \times 10^{17} 2.09 × 1 0 17 位十进制数字(因为 log 10 φ ≈ 0.209 \log_{10} \varphi \approx 0.209 log 10 φ ≈ 0.209 ,所以 log 10 F n ≈ 0.209 n \log_{10} F_n \approx 0.209 n log 10 F n ≈ 0.209 n ),不可能瞬间输出。本章后面提到"快速倍增 O ( log n ) O(\log n) O ( log n ) "都是指下标层面的步数,不是指实际输出大整数的时间。
为什么"翻倍"能成立
这件事值得多想一下。
朴素自底向上加法,是从 F k F_{k} F k 加到 F k + 1 F_{k+1} F k + 1 一步一步走。每一步只看相邻两项,所以每次只能前进 1。
快速倍增能跳,是因为它用了一种"远距离"的依赖关系。具体来说,加法公式 F n + m F_{n+m} F n + m = F n + 1 F_{n+1} F n + 1 F m F_{m} F m + F n F_n F n F m − 1 F_{m-1} F m − 1 告诉我们:F n + m F_{n+m} F n + m 不只是 F n + m − 1 F_{n+m-1} F n + m − 1 和 F n + m − 2 F_{n+m-2} F n + m − 2 的和,它还可以用 F n F_n F n 和 F m F_{m} F m 的乘积表示。
这意味着斐波那契数之间有一种"全局"的乘积关系。这种乘积关系允许我们把 k 步加法压缩成 1 次乘法。
为什么会有这种乘积关系?因为斐波那契递推是线性的。线性递推的解可以写成若干个 r n r^{n} r n 的线性组合,其中 r 是特征方程的根。这意味着 F n + m F_{n+m} F n + m 中的 (n+m) 可以拆成 n 和 m,分别用 r n r^{n} r n 和 r m r^{m} r m 表示。所以乘积形式是自然的。
这个观察在第 8 章(闭式公式)和尾声(一般线性递推)里会再次出现。这里只是先让你看到:"线性"这个性质不只是说递推式里没有 F n 2 F_n^2 F n 2 之类的项,它还隐含着一系列乘积恒等式,而这些恒等式让我们可以跳步。
加法变乘法,是结构允许跳步
到这里可以总结一下快速倍增的精髓。
朴素自底向上已经很聪明了。它发现子问题重叠,所以每个子问题只算一次。但是它只用了"局部"的递推关系:F k F_{k} F k 依赖 F k − 1 F_{k-1} F k − 1 和 F k − 2 F_{k-2} F k − 2 。
快速倍增更进一步。它发现了"全局"的乘积关系:F n + m F_{n+m} F n + m 可以用 F n F_n F n 和 F m F_{m} F m 表示。所以 F 2 k F_{2k} F 2 k 不需要 k 步加法,只需要 1 次乘法。
把加法变成乘法,本质上是利用了递推的线性结构。线性带来可分性,可分性带来跳步。
这是斐波那契给我们的第四个教训。它告诉我们:算法的快慢,不只取决于代码写得多精巧,还取决于我们对底层结构的利用有多深。
一个小尾巴:再加一道恒等式
最后再补一个快速倍增会用到的小技巧。翻倍之后要做"加一",从 (F k F_{k} F k , F k + 1 F_{k+1} F k + 1 ) 变到 (F k + 1 F_{k+1} F k + 1 , F k + 2 F_{k+2} F k + 2 )。F k + 2 = F k + 1 + F k F_{k+2} = F_{k+1} + F_{k} F k + 2 = F k + 1 + F k 是显然的。
但是有时候我们也想从 (F k F_{k} F k , F k + 1 F_{k+1} F k + 1 ) 直接跳到 (F 2 k + 1 F_{2k+1} F 2 k + 1 , F 2 k + 2 F_{2k+2} F 2 k + 2 ),跳两格而不是一格。这种情况怎么算?
F 2 k + 2 = F 2 k + 1 + F 2 k F_{2k+2} = F_{2k+1} + F_{2k} F 2 k + 2 = F 2 k + 1 + F 2 k ,两个值都已经在翻倍公式里算出来了,加一下就行。
更一般地,所有 F n F_n F n 都可以通过"翻倍 + 一步前进"组合出来。具体的实现细节这里不展开,留到下一章讲完矩阵快速幂之后会看得更清楚。矩阵的乘法天然地把"翻倍"和"前进"统一成同一种操作。
下一章我们换一种工具看同一个跳步结构。不再用代数恒等式,改用矩阵幂。结果会发现,快速倍增公式其实是矩阵平方的展开版。
第 6 章 矩阵快速幂
上一章我们用快速倍增把斐波那契做到了 O ( log n ) O(\log n) O ( log n ) 。这一章换一种工具看同一个跳步结构:矩阵快速幂。
矩阵在斐波那契里的位置很有意思。第一次见它的人会觉得"为什么要扯上矩阵",但是看清楚之后会发现,矩阵只是把快速倍增的代数恒等式换了一种紧凑的写法。
把递推写成矩阵
回忆斐波那契递推:F n + 1 = F n + F n − 1 F_{n+1} = F_n + F_{n-1} F n + 1 = F n + F n − 1 。
把它和恒等式 F n = F n F_n = F_n F n = F n 凑在一起,可以写成:
( F n + 1 F n ) = ( F n + F n − 1 F n ) = ( 1 1 1 0 ) ( F n F n − 1 ) .
\begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix}
= \begin{pmatrix} F_n + F_{n-1} \\ F_n \end{pmatrix}
= \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}
\begin{pmatrix} F_n \\ F_{n-1} \end{pmatrix}.
( F n + 1 F n ) = ( F n + F n − 1 F n ) = ( 1 1 1 0 ) ( F n F n − 1 ) .
验算一下右边。第一行:1 ⋅ F n + 1 ⋅ F n − 1 = F n + F n − 1 = F n + 1 1 \cdot F_n + 1 \cdot F_{n-1} = F_n + F_{n-1} = F_{n+1} 1 ⋅ F n + 1 ⋅ F n − 1 = F n + F n − 1 = F n + 1 。对。
第二行:1 ⋅ F n + 0 ⋅ F n − 1 = F n 1 \cdot F_n + 0 \cdot F_{n-1} = F_n 1 ⋅ F n + 0 ⋅ F n − 1 = F n 。对。
把中间那个 2 × 2 2 \times 2 2 × 2 矩阵记作 M M M :
M = ( 1 1 1 0 ) .
M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}.
M = ( 1 1 1 0 ) .
那么递推可以写成 v ( n + 1 ) = M ⋅ v ( n ) v(n+1) = M \cdot v(n) v ( n + 1 ) = M ⋅ v ( n ) ,其中 v ( n ) = ( F n + 1 F n ) v(n) = \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix} v ( n ) = ( F n + 1 F n ) 。
反复用这个关系,从 v ( 0 ) = ( F 1 F 0 ) = ( 1 0 ) v(0) = \begin{pmatrix} F_1 \\ F_0 \end{pmatrix} = \begin{pmatrix} 1 \\ 0 \end{pmatrix} v ( 0 ) = ( F 1 F 0 ) = ( 1 0 ) 出发,得到:
v ( n ) = M ⋅ v ( n − 1 ) = M 2 ⋅ v ( n − 2 ) = ⋯ = M n ⋅ v ( 0 ) .
v(n) = M \cdot v(n-1) = M^2 \cdot v(n-2) = \cdots = M^n \cdot v(0).
v ( n ) = M ⋅ v ( n − 1 ) = M 2 ⋅ v ( n − 2 ) = ⋯ = M n ⋅ v ( 0 ) .
也就是说:
( F n + 1 F n ) = M n ( 1 0 ) .
\begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix}
= M^n \begin{pmatrix} 1 \\ 0 \end{pmatrix}.
( F n + 1 F n ) = M n ( 1 0 ) .
算 F n F_n F n 等价于算 M n M^n M n ,然后看第二项。
这一步把"算斐波那契"和"算矩阵幂"对等起来了。任何能快速算 M n M^n M n 的算法,都能快速算 F n F_n F n 。
矩阵怎么帮忙跳步
算 M n M^{n} M n 听起来好像更复杂了。算一个数的 n 次幂已经够烦,算矩阵的 n 次幂不是更烦?
关键在于:算 a n a^{n} a n (普通数的幂)有快速幂算法,O ( log n ) O(\log n) O ( log n ) 次乘法搞定。同样的算法可以搬到矩阵上,得到矩阵快速幂。
普通数的快速幂是这样的:
a 0 = 1 a^0=1 a 0 = 1
a 1 = a a^1=a a 1 = a
a 2 k = ( a k ) 2 a^{2k}=(a^k)^2 a 2 k = ( a k ) 2
a 2 k + 1 = a ⋅ a 2 k a^{2k+1}=a\cdot a^{2k} a 2 k + 1 = a ⋅ a 2 k
按 n n n 的二进制位从低到高走,每遇到一个 1 1 1 就把当前平方过的 a 2 i a^{2^i} a 2 i 乘进结果。总共 O ( log n ) O(\log n) O ( log n ) 次乘法。
矩阵快速幂完全一样,只是把「数 a a a 」换成「矩阵 M M M 」,把"乘法"换成"矩阵乘法":
M 0 = I M^0=I M 0 = I (单位矩阵)
M 1 = M M^1=M M 1 = M
M 2 k = ( M k ) 2 M^{2k}=(M^k)^2 M 2 k = ( M k ) 2
M 2 k + 1 = M ⋅ M 2 k M^{2k+1}=M\cdot M^{2k} M 2 k + 1 = M ⋅ M 2 k
按 n n n 的二进制位走,O ( log n ) O(\log n) O ( log n ) 次矩阵乘法。每次矩阵乘法是 2 × 2 2 \times 2 2 × 2 矩阵相乘,8 次乘法和 4 次加法。
所以矩阵快速幂算 F n F_n F n 的总时间是 O ( log n ) O(\log n) O ( log n ) 次大数运算,每次大数运算包含常数次乘法和加法。和快速倍增一样是 O ( log n ) O(\log n) O ( log n ) 。
跑一下 n = 13 n=13 n = 13
用矩阵快速幂算 F 13 F_{13} F 13 。n = 13 = 110 1 2 n=13=1101_2 n = 13 = 110 1 2 。从结果 r e s = I \mathrm{res}=I res = I (单位矩阵),b a s e = M \mathrm{base}=M base = M 开始,按二进制位从低到高走:
最低位 1:res = res · base = I · M = M。base = base 的平方 = M 2 M^{2} M 2 。
下一位 0:res 不变。base = base 的平方 = M 4 M^{4} M 4 。
下一位 1:res = res · base = M · M 4 M^{4} M 4 = M 5 M^{5} M 5 。base = base 的平方 = M 8 M^{8} M 8 。
最高位 1:res = res · base = M 5 M^{5} M 5 · M 8 M^{8} M 8 = M 13 M^{13} M 13 。
4 步矩阵乘法(不算单位矩阵那次),得到 M 13 M^{13} M 13 。然后 F 13 F_{13} F 13 = M 13 M^{13} M 13 的第二行第一列(或第一行第二列,下面会看到两个都行)。
具体值 M 13 M^{13} M 13 等于什么?我们下一章会证 M n = ( F n + 1 F n F n F n − 1 ) M^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix} M n = ( F n + 1 F n F n F n − 1 ) ,所以 M 13 = ( F 14 F 13 F 13 F 12 ) = ( 377 233 233 144 ) M^{13} = \begin{pmatrix} F_{14} & F_{13} \\ F_{13} & F_{12} \end{pmatrix} = \begin{pmatrix} 377 & 233 \\ 233 & 144 \end{pmatrix} M 13 = ( F 14 F 13 F 13 F 12 ) = ( 377 233 233 144 ) 。读出 F 13 = 233 F_{13} = 233 F 13 = 233 。
对。
矩阵快速幂和快速倍增是同一件事
到这里你可能会想:矩阵快速幂和快速倍增,到底有什么关系?
它们做的事情完全一样。都是用 n 的二进制位控制翻倍,O ( log n ) O(\log n) O ( log n ) 步搞定。
更精确地说,快速倍增的两个公式:
F 2 k = F k ⋅ ( 2 F k + 1 − F k ) , F 2 k + 1 = F k 2 + F k + 1 2 .
F_{2k} = F_k \cdot (2 F_{k+1} - F_k), \qquad F_{2k+1} = F_k^2 + F_{k+1}^2.
F 2 k = F k ⋅ ( 2 F k + 1 − F k ) , F 2 k + 1 = F k 2 + F k + 1 2 .
可以从矩阵平方 M 2 k = ( M k ) 2 M^{2k} = (M^k)^2 M 2 k = ( M k ) 2 直接推出来。
我们算一下 ( M k ) 2 (M^k)^2 ( M k ) 2 。假设 M k = ( a b b c ) M^k = \begin{pmatrix} a & b \\ b & c \end{pmatrix} M k = ( a b b c ) (这个形式下一章会证,所有 M n M^n M n 都是对称的,且 entry 是斐波那契数)。那么:
( M k ) 2 = ( a b b c ) ⋅ ( a b b c ) = ( a 2 + b 2 a b + b c a b + b c b 2 + c 2 ) = ( a 2 + b 2 b ( a + c ) b ( a + c ) b 2 + c 2 ) .
(M^k)^2 = \begin{pmatrix} a & b \\ b & c \end{pmatrix} \cdot \begin{pmatrix} a & b \\ b & c \end{pmatrix} = \begin{pmatrix} a^2 + b^2 & ab + bc \\ ab + bc & b^2 + c^2 \end{pmatrix} = \begin{pmatrix} a^2 + b^2 & b(a+c) \\ b(a+c) & b^2 + c^2 \end{pmatrix}.
( M k ) 2 = ( a b b c ) ⋅ ( a b b c ) = ( a 2 + b 2 ab + b c ab + b c b 2 + c 2 ) = ( a 2 + b 2 b ( a + c ) b ( a + c ) b 2 + c 2 ) .
而 M 2 k M^{2k} M 2 k 也具有相同的形式 ( F 2 k + 1 F 2 k F 2 k F 2 k − 1 ) \begin{pmatrix} F_{2k+1} & F_{2k} \\ F_{2k} & F_{2k-1} \end{pmatrix} ( F 2 k + 1 F 2 k F 2 k F 2 k − 1 ) 。所以:
F 2 k + 1 = a 2 + b 2 = F k + 1 2 + F k 2 , F 2 k = b ( a + c ) = F k ⋅ ( F k + 1 + F k − 1 ) = F k ⋅ ( 2 F k + 1 − F k ) .
\begin{aligned}
F_{2k+1} &= a^2 + b^2 = F_{k+1}^2 + F_k^2, \\
F_{2k} &= b(a+c) = F_k \cdot (F_{k+1} + F_{k-1}) = F_k \cdot (2 F_{k+1} - F_k).
\end{aligned}
F 2 k + 1 F 2 k = a 2 + b 2 = F k + 1 2 + F k 2 , = b ( a + c ) = F k ⋅ ( F k + 1 + F k − 1 ) = F k ⋅ ( 2 F k + 1 − F k ) .
第二个等号用了 F k − 1 = F k + 1 − F k F_{k-1} = F_{k+1} - F_k F k − 1 = F k + 1 − F k 。
这就是快速倍增公式。它从矩阵平方 M 2 k = ( M k ) 2 M^{2k} = (M^k)^2 M 2 k = ( M k ) 2 直接掉出来。
所以快速倍增其实是矩阵快速幂的"展开版"。它把 2 × 2 2 \times 2 2 × 2 矩阵乘法里 8 次乘法 4 次加法,简化成了 3 次乘法 2 次加法(利用了 M n M^n M n 的对称性,省掉了一些项)。常数小一点,但本质上是同一件事。
为什么用矩阵快速幂
既然快速倍增常数更小,为什么还要讲矩阵快速幂?
两个理由。
第一,矩阵形式更紧凑。M n M^{n} M n 一个符号就表达了"翻 n 次状态转移"这件事,不需要写两个公式。在很多场合,紧凑的表达比省一两次乘法更重要。
第二,矩阵形式更通用。斐波那契的伴随矩阵 M M M 是 ( 1 1 1 0 ) \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} ( 1 1 1 0 ) ,但其他线性递推有其他伴随矩阵。比如 Pell 数列 P n = 2 P ( n − 1 ) + P ( n − 2 ) P_n = 2 P(n-1) + P(n-2) P n = 2 P ( n − 1 ) + P ( n − 2 ) 的伴随矩阵是 ( 2 1 1 0 ) \begin{pmatrix} 2 & 1 \\ 1 & 0 \end{pmatrix} ( 2 1 1 0 ) 。一旦学会矩阵快速幂,所有二阶线性递推都可以套同一套算法,只是矩阵 entry 换一下。
更重要的是,矩阵形式让"线性递推"和"线性变换迭代"对应起来。学会这种对应之后,看见任何线性递推,第一反应都是"它的伴随矩阵是什么"。这是第 7 章的主题。
一个具体的代码对比
让我们把两个算法的代码放在一起对比一下。
快速倍增(递归版):
function fast_doubling(n):
if n == 0: return (0, 1)
a, b = fast_doubling(n // 2)
c = a * (2 * b - a)
d = a * a + b * b
if n % 2 == 0:
return (c, d)
else:
return (d, c + d)
矩阵快速幂(迭代版):
function mat_mul(A, B):
return [[A[0][0]*B[0][0] + A[0][1]*B[1][0], A[0][0]*B[0][1] + A[0][1]*B[1][1]],
[A[1][0]*B[0][0] + A[1][1]*B[1][0], A[1][0]*B[0][1] + A[1][1]*B[1][1]]]
function mat_pow(n):
result = [[1, 0], [0, 1]] # 单位矩阵
base = [[1, 1], [1, 0]]
while n > 0:
if n % 2 == 1:
result = mat_mul(result, base)
base = mat_mul(base, base)
n = n // 2
return result
两个算法都跑 O ( log n ) O(\log n) O ( log n ) 次循环。快速倍增每次循环 3 次乘法 2 次加法。矩阵快速幂每次循环 8 次乘法 4 次加法(如果 result 也要更新就是 16 次乘法 8 次加法)。
所以快速倍增的常数确实小。但是矩阵快速幂的代码更整齐:所有操作都是"矩阵乘矩阵",没有快速倍增里那种"2 F k + 1 F_{k+1} F k + 1 - F k F_{k} F k "看起来有点神秘的公式。
写代码的时候,如果你只关心斐波那契,用快速倍增。如果你要处理一般线性递推,或者要和第 7 章的状态视角接上,用矩阵快速幂。
本章小结
我们把斐波那契递推改写成了矩阵形式 v(n+1) = M · v(n),然后把"算 F n F_n F n "对等到"算 M n M^{n} M n "。
矩阵快速幂用和普通快速幂完全一样的二进制分解思路,O ( log n ) O(\log n) O ( log n ) 次矩阵乘法搞定 M n M^{n} M n 。
快速倍增和矩阵快速幂本质上是同一件事。快速倍增是矩阵平方 ( M k ) 2 (M^k)^2 ( M k ) 2 在斐波那契特殊情况下的展开版,常数小一点。
矩阵形式更紧凑、更通用。任何二阶线性递推都有自己的伴随矩阵,矩阵快速幂可以套用到所有这些递推上。
到这一章为止,我们已经把"算 F n F_n F n "从 O ( φ n ) O(\varphi^n) O ( φ n ) 一路压到 O ( log n ) O(\log n) O ( log n ) 。每一步都来自对递推结构的更深入利用:第 4 章发现子问题重叠,第 5 章发现线性结构带来乘积恒等式,这一章发现矩阵把乘积恒等式压缩成一个统一的运算。
但是矩阵在斐波那契里的角色,远不止"加速算法"这一项。下一章我们会把矩阵拿来当一种语言看:让矩阵从"加速工具"变成"状态变化的描述"。我们会发现,矩阵不是凭空出现的表格,它可以从"递推要继续,到底要记住什么"这个最朴素的问题里长出来。
第 7 章 矩阵作为状态语言
上一章我们用矩阵快速幂算 F n F_n F n 。矩阵 M M M 在那里只是一个工具,我们没多想它为什么是那个样子,为什么刚好 ( 1 1 1 0 ) \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} ( 1 1 1 0 ) 。
这一章换一个视角:让矩阵从"工具"变成"语言"。我们要从"递推要继续,到底要记住什么"这个最朴素的问题开始,让矩阵自己走出来。
递推要继续,到底要记住什么
斐波那契递推说:F n = F n − 1 + F n − 2 F_{n} = F_{n-1} + F_{n-2} F n = F n − 1 + F n − 2 。
我们要从第 n-1 步推到第 n 步。这个动作需要什么信息?
不是 F n − 1 F_{n-1} F n − 1 这一个数。是 F n − 1 F_{n-1} F n − 1 和 F n − 2 F_{n-2} F n − 2 两个数。只有 F n − 1 F_{n-1} F n − 1 不够。算 F n F_n F n 要 F n − 2 F_{n-2} F n − 2 ,而 F n − 2 F_{n-2} F n − 2 在算 F n − 1 F_{n-1} F n − 1 的时候被"用掉"了,如果没有专门记下来,就丢了。
也就是说,斐波那契递推的状态不是一个数,是一对数:( F n − 1 , F n − 2 ) (F_{n-1}, F_{n-2}) ( F n − 1 , F n − 2 ) 。或者等价地写成 ( F n , F n − 1 ) (F_n, F_{n-1}) ( F n , F n − 1 ) ,后一项和当前项。
这一点是关键。它解释了为什么斐波那契不像"前一项加 1"那么简单。一维状态推不下去,状态本身就是二维的。
让状态向量是 v ( n ) = ( F n + 1 F n ) v(n) = \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix} v ( n ) = ( F n + 1 F n ) 。
那从 v ( n ) v(n) v ( n ) 到 v ( n + 1 ) v(n+1) v ( n + 1 ) 是什么?要把 ( F n + 1 F n ) \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix} ( F n + 1 F n ) 变成 ( F n + 2 F n + 1 ) \begin{pmatrix} F_{n+2} \\ F_{n+1} \end{pmatrix} ( F n + 2 F n + 1 ) 。
按定义 F n + 2 = F n + 1 + F n F_{n+2} = F_{n+1} + F_n F n + 2 = F n + 1 + F n 。所以新的第一项是 F n + 1 + F n F_{n+1} + F_n F n + 1 + F n ,新的第二项是 F n + 1 F_{n+1} F n + 1 。
写成向量:
( F n + 2 F n + 1 ) = ( F n + 1 + F n F n + 1 ) = ( 1 1 1 0 ) ( F n + 1 F n ) .
\begin{pmatrix} F_{n+2} \\ F_{n+1} \end{pmatrix}
= \begin{pmatrix} F_{n+1} + F_n \\ F_{n+1} \end{pmatrix}
= \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}
\begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix}.
( F n + 2 F n + 1 ) = ( F n + 1 + F n F n + 1 ) = ( 1 1 1 0 ) ( F n + 1 F n ) .
中间那个 ( 1 1 1 0 ) \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} ( 1 1 1 0 ) 就是把 ( F n + 1 , F n ) (F_{n+1}, F_n) ( F n + 1 , F n ) 变成 ( F n + 2 , F n + 1 ) (F_{n+2}, F_{n+1}) ( F n + 2 , F n + 1 ) 的"动作"。
记它为 M M M :
M = ( 1 1 1 0 ) .
M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}.
M = ( 1 1 1 0 ) .
那么 v ( n + 1 ) = M ⋅ v ( n ) v(n+1) = M \cdot v(n) v ( n + 1 ) = M ⋅ v ( n ) 。
这就是斐波那契递推的矩阵形式。
矩阵是什么?这里它就是一个动作
我们刚才做的事情是:把"从一步到下一步"这个动作,写成一个矩阵乘法。
这一点要慢慢体会。矩阵 M = ( 1 1 1 0 ) M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} M = ( 1 1 1 0 ) 在这里不是一组随便摆放的数。它是一个"动作"。这个动作把状态向量从 ( F n + 1 , F n ) (F_{n+1}, F_n) ( F n + 1 , F n ) 推到 ( F n + 2 , F n + 1 ) (F_{n+2}, F_{n+1}) ( F n + 2 , F n + 1 ) 。
矩阵的每一行,告诉你新状态的每一项是怎么从旧状态组合出来的:
第一行 [ 1 , 1 ] [1, 1] [ 1 , 1 ] :新状态的第一项 = 1 ⋅ F n + 1 + 1 ⋅ F n = F n + 1 + F n = F n + 2 = 1 \cdot F_{n+1} + 1 \cdot F_n = F_{n+1} + F_n = F_{n+2} = 1 ⋅ F n + 1 + 1 ⋅ F n = F n + 1 + F n = F n + 2 。这是定义。
第二行 [ 1 , 0 ] [1, 0] [ 1 , 0 ] :新状态的第二项 = 1 ⋅ F n + 1 + 0 ⋅ F n = F n + 1 = 1 \cdot F_{n+1} + 0 \cdot F_n = F_{n+1} = 1 ⋅ F n + 1 + 0 ⋅ F n = F n + 1 。这是把上一项"挪下来"。
所以矩阵的两行,分别对应两个动作:第一行做"加和",把前两项加起来变成下一项。第二行做"挪位",把当前项变成历史项。
这两件事加起来,就是斐波那契递推的全部动作。矩阵把它压缩到 4 个数里,但每个数都有意义。
这就是矩阵在斐波那契里出现的方式。它从"我们要记住什么、怎么变到下一步"这个需求里长出来。
把它乘 n 次看看
既然 v ( n + 1 ) = M ⋅ v ( n ) v(n+1) = M \cdot v(n) v ( n + 1 ) = M ⋅ v ( n ) ,那 v ( n ) = M ⋅ v ( n − 1 ) = M 2 ⋅ v ( n − 2 ) = ⋯ = M n ⋅ v ( 0 ) v(n) = M \cdot v(n-1) = M^2 \cdot v(n-2) = \cdots = M^n \cdot v(0) v ( n ) = M ⋅ v ( n − 1 ) = M 2 ⋅ v ( n − 2 ) = ⋯ = M n ⋅ v ( 0 ) 。
初始向量 v ( 0 ) = ( F 1 F 0 ) = ( 1 0 ) v(0) = \begin{pmatrix} F_1 \\ F_0 \end{pmatrix} = \begin{pmatrix} 1 \\ 0 \end{pmatrix} v ( 0 ) = ( F 1 F 0 ) = ( 1 0 ) 。所以:
( F n + 1 F n ) = M n ( 1 0 ) .
\begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix}
= M^n \begin{pmatrix} 1 \\ 0 \end{pmatrix}.
( F n + 1 F n ) = M n ( 1 0 ) .
这个等式说:算 F n F_n F n 等价于算 M n M^n M n 然后看第二项。
到这一步,矩阵形式已经做了两件事:
把斐波那契递推改写成了一个"线性变换的幂"。
把"算 F n F_n F n "和"算 M n M^n M n "对等起来。
第二件事意味着,任何能快速算 M n M^n M n 的算法,都能快速算 F n F_n F n 。这是上一章矩阵快速幂的入口。
但更重要的是第一件事:递推被改写成"线性变换迭代 n n n 次"。这种改写揭示了斐波那契递推的某种代数结构。下面我们看这种结构能告诉我们什么。
看看 M n M^n M n 长什么样
把 M M M 的前几次幂算一下:
M 1 = ( 1 1 1 0 ) ,
M^1 = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix},
M 1 = ( 1 1 1 0 ) ,
M 2 = M ⋅ M = ( 1 ⋅ 1 + 1 ⋅ 1 1 ⋅ 1 + 1 ⋅ 0 1 ⋅ 1 + 0 ⋅ 1 1 ⋅ 1 + 0 ⋅ 0 ) = ( 2 1 1 1 ) ,
M^2 = M \cdot M = \begin{pmatrix} 1\cdot 1+1\cdot 1 & 1\cdot 1+1\cdot 0 \\ 1\cdot 1+0\cdot 1 & 1\cdot 1+0\cdot 0 \end{pmatrix} = \begin{pmatrix} 2 & 1 \\ 1 & 1 \end{pmatrix},
M 2 = M ⋅ M = ( 1 ⋅ 1 + 1 ⋅ 1 1 ⋅ 1 + 0 ⋅ 1 1 ⋅ 1 + 1 ⋅ 0 1 ⋅ 1 + 0 ⋅ 0 ) = ( 2 1 1 1 ) ,
M 3 = M 2 ⋅ M = ( 2 ⋅ 1 + 1 ⋅ 1 2 ⋅ 1 + 1 ⋅ 0 1 ⋅ 1 + 1 ⋅ 1 1 ⋅ 1 + 1 ⋅ 0 ) = ( 3 2 2 1 ) ,
M^3 = M^2 \cdot M = \begin{pmatrix} 2\cdot 1+1\cdot 1 & 2\cdot 1+1\cdot 0 \\ 1\cdot 1+1\cdot 1 & 1\cdot 1+1\cdot 0 \end{pmatrix} = \begin{pmatrix} 3 & 2 \\ 2 & 1 \end{pmatrix},
M 3 = M 2 ⋅ M = ( 2 ⋅ 1 + 1 ⋅ 1 1 ⋅ 1 + 1 ⋅ 1 2 ⋅ 1 + 1 ⋅ 0 1 ⋅ 1 + 1 ⋅ 0 ) = ( 3 2 2 1 ) ,
M 4 = M 3 ⋅ M = ( 5 3 3 2 ) , M 5 = ( 8 5 5 3 ) .
M^4 = M^3 \cdot M = \begin{pmatrix} 5 & 3 \\ 3 & 2 \end{pmatrix}, \qquad
M^5 = \begin{pmatrix} 8 & 5 \\ 5 & 3 \end{pmatrix}.
M 4 = M 3 ⋅ M = ( 5 3 3 2 ) , M 5 = ( 8 5 5 3 ) .
看出来了吗?M n M^n M n 总是这种形式:
M n = ( F n + 1 F n F n F n − 1 ) .
M^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix}.
M n = ( F n + 1 F n F n F n − 1 ) .
这个形式可以数学归纳法证明。
n = 1 n = 1 n = 1 时,M 1 = ( 1 1 1 0 ) = ( F 2 F 1 F 1 F 0 ) M^1 = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} = \begin{pmatrix} F_2 & F_1 \\ F_1 & F_0 \end{pmatrix} M 1 = ( 1 1 1 0 ) = ( F 2 F 1 F 1 F 0 ) 。对。
假设 M k = ( F k + 1 F k F k F k − 1 ) M^k = \begin{pmatrix} F_{k+1} & F_k \\ F_k & F_{k-1} \end{pmatrix} M k = ( F k + 1 F k F k F k − 1 ) 。那 M k + 1 = M k ⋅ M M^{k+1} = M^k \cdot M M k + 1 = M k ⋅ M :
M k + 1 = ( F k + 1 F k F k F k − 1 ) ( 1 1 1 0 ) = ( F k + 1 + F k F k + 1 F k + F k − 1 F k ) = ( F k + 2 F k + 1 F k + 1 F k ) .
M^{k+1} = \begin{pmatrix} F_{k+1} & F_k \\ F_k & F_{k-1} \end{pmatrix} \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} = \begin{pmatrix} F_{k+1}+F_k & F_{k+1} \\ F_k+F_{k-1} & F_k \end{pmatrix} = \begin{pmatrix} F_{k+2} & F_{k+1} \\ F_{k+1} & F_k \end{pmatrix}.
M k + 1 = ( F k + 1 F k F k F k − 1 ) ( 1 1 1 0 ) = ( F k + 1 + F k F k + F k − 1 F k + 1 F k ) = ( F k + 2 F k + 1 F k + 1 F k ) .
确实等于 ( F k + 2 F k + 1 F k + 1 F k ) \begin{pmatrix} F_{k+2} & F_{k+1} \\ F_{k+1} & F_k \end{pmatrix} ( F k + 2 F k + 1 F k + 1 F k ) 。归纳成立。
所以 M n M^n M n 的四个 entry 都是斐波那契数。这给出了一组恒等式。
这件事本身就很妙:一个看起来毫无关系的矩阵,反复自乘之后,每个位置上都是一个斐波那契数。而且位置有讲究,它反映了斐波那契数之间的对称性。
矩阵形式立刻给出恒等式
既然 M n M^n M n 的形式固定,那 M n ⋅ M m = M n + m M^n \cdot M^m = M^{n+m} M n ⋅ M m = M n + m 就给出斐波那契恒等式。
具体算一下。M n = ( F n + 1 F n F n F n − 1 ) M^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix} M n = ( F n + 1 F n F n F n − 1 ) ,M m = ( F m + 1 F m F m F m − 1 ) M^m = \begin{pmatrix} F_{m+1} & F_m \\ F_m & F_{m-1} \end{pmatrix} M m = ( F m + 1 F m F m F m − 1 ) 。它们的乘积等于 M n + m = ( F n + m + 1 F n + m F n + m F n + m − 1 ) M^{n+m} = \begin{pmatrix} F_{n+m+1} & F_{n+m} \\ F_{n+m} & F_{n+m-1} \end{pmatrix} M n + m = ( F n + m + 1 F n + m F n + m F n + m − 1 ) 。
把左边乘开:
M n ⋅ M m = ( F n + 1 F m + 1 + F n F m F n + 1 F m + F n F m − 1 F n F m + 1 + F n − 1 F m F n F m + F n − 1 F m − 1 ) .
M^n \cdot M^m = \begin{pmatrix} F_{n+1} F_{m+1} + F_n F_m & F_{n+1} F_m + F_n F_{m-1} \\ F_n F_{m+1} + F_{n-1} F_m & F_n F_m + F_{n-1} F_{m-1} \end{pmatrix}.
M n ⋅ M m = ( F n + 1 F m + 1 + F n F m F n F m + 1 + F n − 1 F m F n + 1 F m + F n F m − 1 F n F m + F n − 1 F m − 1 ) .
对比右边 ( F n + m + 1 F n + m F n + m F n + m − 1 ) \begin{pmatrix} F_{n+m+1} & F_{n+m} \\ F_{n+m} & F_{n+m-1} \end{pmatrix} ( F n + m + 1 F n + m F n + m F n + m − 1 ) ,得到:
左上角:F n + 1 F m + 1 + F n F m = F n + m + 1 F_{n+1} F_{m+1} + F_n F_m = F_{n+m+1} F n + 1 F m + 1 + F n F m = F n + m + 1
右上角:F n + 1 F m + F n F m − 1 = F n + m F_{n+1} F_m + F_n F_{m-1} = F_{n+m} F n + 1 F m + F n F m − 1 = F n + m
左下角:F n F m + 1 + F n − 1 F m = F n + m F_n F_{m+1} + F_{n-1} F_m = F_{n+m} F n F m + 1 + F n − 1 F m = F n + m
右下角:F n F m + F n − 1 F m − 1 = F n + m − 1 F_n F_m + F_{n-1} F_{m-1} = F_{n+m-1} F n F m + F n − 1 F m − 1 = F n + m − 1
这就是从矩阵形式直接掉出来的恒等式。其中右上角那个就是我们第 5 章用过的加法公式:
F n + m = F n + 1 F m + F n F m − 1 .
F_{n+m} = F_{n+1} F_m + F_n F_{m-1}.
F n + m = F n + 1 F m + F n F m − 1 .
当时我们说这个公式"成立",但是要单独验证。现在从矩阵来看,它根本就是矩阵乘法的必然结果。M n ⋅ M m M^n \cdot M^m M n ⋅ M m 必须等于 M n + m M^{n+m} M n + m ,所以右上角的 entry 必须给出 F n + m F_{n+m} F n + m 的乘积公式。
矩阵在这里做了一件事:把若干个看似独立的恒等式,统一成同一个事实的不同 entry。这是矩阵作为"语言"的威力。它把零散的代数恒等式压缩成一个紧凑的几何结构。
矩阵作为"状态变化的语言"
这一章的核心,可以总结成一句话:
矩阵可以从"我要记住两个数"这个朴素需求里长出来。它是状态变化的语言。
很多人对矩阵的第一印象是"一个矩形数组"。这个印象没错,但也没用。它没告诉你矩阵为什么重要,没告诉你为什么要做矩阵乘法这种奇怪的操作。
矩阵真正有力的地方,是它能把"一个状态怎么变成下一个状态"这种动作压缩进一个紧凑的对象里。状态是一个向量,动作是一个矩阵,状态变化就是向量乘矩阵。如果同一个动作反复施加,那就是矩阵的幂。
斐波那契是这种视角的最简单样本。状态是二维向量 ( F n + 1 , F n ) (F_{n+1}, F_n) ( F n + 1 , F n ) ,动作是矩阵 M = ( 1 1 1 0 ) M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} M = ( 1 1 1 0 ) ,状态变化就是 v ( n + 1 ) = M ⋅ v ( n ) v(n+1) = M \cdot v(n) v ( n + 1 ) = M ⋅ v ( n ) 。F n F_n F n 是 M n M^n M n 的一个 entry。
这种视角的好处是它天然地推广。任何"线性递推",状态在每一步是前几步的线性组合,都可以写成矩阵形式。一旦写成矩阵形式,就可以用矩阵幂来快速计算,可以用特征值来研究增长,可以用模运算来研究周期。
第 5 章我们用矩阵快速幂做了加速算法。这一章我们把矩阵拿来当语言。同一个 M M M ,两种用法,对应两种视角。
矩阵从"我要记住两个数"这个朴素需求里走出来,最终变成了一种通用的语言。下一章我们看这种语言如何自然地把 5 \sqrt{5} 5 引出来。
第 8 章 闭式公式与 5 \sqrt5 5 的来历
到上一章为止,我们一直在做一件事:把 F n F_n F n 算出来。朴素递归、记忆化、自底向上、快速倍增、矩阵快速幂,所有这些算法,本质上都是按某种顺序做加法和乘法,逐步逼近 F n F_n F n 。
这一章换一个问题:能不能不用递推,而是一步写出 F n F_n F n ?也就是说,能不能有一个只含 n n n 的公式,把 n n n 代进去就得到答案?
这种公式叫闭式公式。它的意思是:F n F_n F n 可以用 n n n 的有限次运算直接表示,不需要循环,不需要递归,不需要先算前面的项。
斐波那契数列确实有闭式公式,而且这个公式里出现了 5 \sqrt5 5 。一个由整数加法生成的数列,闭式公式里竟然有一个无理数。第一次看到这件事,很多人会觉得诧异。这一章就把 5 \sqrt5 5 的来源讲清楚。
先猜一个形状
斐波那契递推是
F n = F n − 1 + F n − 2 .
F_n=F_{n-1}+F_{n-2}.
F n = F n − 1 + F n − 2 .
这是线性递推。线性递推有一个常见试探:先看有没有形如 r n r^n r n 的解。于是先假设
F n = A r n ,
F_n=A r^n,
F n = A r n ,
其中 A A A 是常数,r r r 是待定的数。
把它代入递推:
A r n = A r n − 1 + A r n − 2 .
A r^n=A r^{n-1}+A r^{n-2}.
A r n = A r n − 1 + A r n − 2 .
两边除以 A r n − 2 A r^{n-2} A r n − 2 ,得到
也就是
r 2 − r − 1 = 0.
r^2-r-1=0.
r 2 − r − 1 = 0.
这个二次方程的两个根是
r = 1 ± 5 2 .
r=\frac{1\pm\sqrt5}{2}.
r = 2 1 ± 5 .
记作
φ = 1 + 5 2 , ψ = 1 − 5 2 .
\varphi=\frac{1+\sqrt5}{2},
\qquad
\psi=\frac{1-\sqrt5}{2}.
φ = 2 1 + 5 , ψ = 2 1 − 5 .
φ \varphi φ 是常说的黄金比例,ψ \psi ψ 是它的另一个共轭根,约为 − 0.618 -0.618 − 0.618 。
到这里,5 \sqrt5 5 已经出现了。它是方程 r 2 − r − 1 = 0 r^2-r-1=0 r 2 − r − 1 = 0 的判别式带来的结果。
用初值定出系数
因为 φ n \varphi^n φ n 和 ψ n \psi^n ψ n 都满足同一个递推,它们的线性组合也满足递推。因此一般形式可以写成
F n = A φ n + B ψ n .
F_n=A\varphi^n+B\psi^n.
F n = A φ n + B ψ n .
用初值 F 0 = 0 F_0=0 F 0 = 0 和 F 1 = 1 F_1=1 F 1 = 1 来确定 A , B A,B A , B 。
当 n = 0 n=0 n = 0 时:
F 0 = A + B = 0 ,
F_0=A+B=0,
F 0 = A + B = 0 ,
所以 B = − A B=-A B = − A 。
当 n = 1 n=1 n = 1 时:
F 1 = A φ + B ψ = 1.
F_1=A\varphi+B\psi=1.
F 1 = A φ + B ψ = 1.
代入 B = − A B=-A B = − A ,得到
A ( φ − ψ ) = 1.
A(\varphi-\psi)=1.
A ( φ − ψ ) = 1.
又因为
φ − ψ = 5 ,
\varphi-\psi=\sqrt5,
φ − ψ = 5 ,
所以
A = 1 5 , B = − 1 5 .
A=\frac1{\sqrt5},
\qquad
B=-\frac1{\sqrt5}.
A = 5 1 , B = − 5 1 .
于是得到 Binet 公式:
F n = φ n − ψ n 5 .
F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.
F n = 5 φ n − ψ n .
把 φ \varphi φ 和 ψ \psi ψ 展开,就是
F n = ( 1 + 5 2 ) n − ( 1 − 5 2 ) n 5 .
F_n=
\frac{
\left(\frac{1+\sqrt5}{2}\right)^n-
\left(\frac{1-\sqrt5}{2}\right)^n
}{\sqrt5}.
F n = 5 ( 2 1 + 5 ) n − ( 2 1 − 5 ) n .
小例子检查
n = 0 n=0 n = 0 时,
F 0 = 1 − 1 5 = 0.
F_0=\frac{1-1}{\sqrt5}=0.
F 0 = 5 1 − 1 = 0.
n = 1 n=1 n = 1 时,
F 1 = φ − ψ 5 = 1.
F_1=\frac{\varphi-\psi}{\sqrt5}=1.
F 1 = 5 φ − ψ = 1.
n = 2 n=2 n = 2 时,利用 φ 2 = φ + 1 \varphi^2=\varphi+1 φ 2 = φ + 1 和 ψ 2 = ψ + 1 \psi^2=\psi+1 ψ 2 = ψ + 1 ,有
F 2 = φ 2 − ψ 2 5 = ( φ + 1 ) − ( ψ + 1 ) 5 = 1.
F_2=
\frac{\varphi^2-\psi^2}{\sqrt5}
=\frac{(\varphi+1)-(\psi+1)}{\sqrt5}
=1.
F 2 = 5 φ 2 − ψ 2 = 5 ( φ + 1 ) − ( ψ + 1 ) = 1.
n = 10 n=10 n = 10 时,
F 10 = φ 10 − ψ 10 5 = 55.
F_{10}=\frac{\varphi^{10}-\psi^{10}}{\sqrt5}=55.
F 10 = 5 φ 10 − ψ 10 = 55.
如果用小数近似计算,φ 10 ≈ 122.992 \varphi^{10}\approx122.992 φ 10 ≈ 122.992 ,ψ 10 ≈ 0.00813 \psi^{10}\approx0.00813 ψ 10 ≈ 0.00813 ,两者相减后再除以 5 \sqrt5 5 ,结果约为 55 55 55 。
5 \sqrt5 5 为什么会消失
Binet 公式里同时出现了 + 5 +\sqrt5 + 5 和 − 5 -\sqrt5 − 5 :
φ = 1 + 5 2 , ψ = 1 − 5 2 .
\varphi=\frac{1+\sqrt5}{2},
\qquad
\psi=\frac{1-\sqrt5}{2}.
φ = 2 1 + 5 , ψ = 2 1 − 5 .
把 ( 1 + 5 ) n (1+\sqrt5)^n ( 1 + 5 ) n 用二项式定理展开:
( 1 + 5 ) n = ∑ k = 0 n ( n k ) ( 5 ) k .
(1+\sqrt5)^n
=
\sum_{k=0}^{n}\binom{n}{k}(\sqrt5)^k.
( 1 + 5 ) n = k = 0 ∑ n ( k n ) ( 5 ) k .
\sum_{k=0}^{n}\binom{n}{k}(\sqrt5)^k.
偶数次项是有理数,奇数次项是 5 \sqrt5 5 的有理数倍。( 1 − 5 ) n (1-\sqrt5)^n ( 1 − 5 ) n 的偶数次项相同,奇数次项符号相反。
所以
( 1 + 5 ) n − ( 1 − 5 ) n
(1+\sqrt5)^n-(1-\sqrt5)^n
( 1 + 5 ) n − ( 1 − 5 ) n
会把偶数次项抵消,只留下奇数次项。留下的每一项都带一个 5 \sqrt5 5 ,再除以 Binet 公式分母里的 5 \sqrt5 5 ,就回到了有理数。第 9 章会进一步说明它为什么恰好是整数。
黄金比例在这里是什么角色
φ \varphi φ 和斐波那契数列确实关系密切。Binet 公式告诉我们,F n F_n F n 的主导项是
φ n 5 .
\frac{\varphi^n}{\sqrt5}.
5 φ n .
因为 ∣ ψ ∣ < 1 |\psi|<1 ∣ ψ ∣ < 1 ,当 n n n 变大时,ψ n \psi^n ψ n 会迅速接近 0 0 0 。所以 F n F_n F n 越来越接近 φ n / 5 \varphi^n/\sqrt5 φ n / 5 。
但这里要小心:φ \varphi φ 出现,是因为它是方程
的较大根。它不是神秘装饰,也不是从几何里硬贴过来的常数。换一个二阶线性递推,特征方程变了,特征根也会变。
比如
a n = 3 a n − 1 − 2 a n − 2
a_n=3a_{n-1}-2a_{n-2}
a n = 3 a n − 1 − 2 a n − 2
的特征方程是
r 2 = 3 r − 2 ,
r^2=3r-2,
r 2 = 3 r − 2 ,
也就是
r 2 − 3 r + 2 = 0.
r^2-3r+2=0.
r 2 − 3 r + 2 = 0.
它的根是 1 1 1 和 2 2 2 ,不会出现黄金比例。
矩阵视角下的同一件事
上一章的状态转移矩阵是
M = ( 1 1 1 0 ) .
M=
\begin{pmatrix}
1&1\\
1&0
\end{pmatrix}.
M = ( 1 1 1 0 ) .
它的特征值来自
det ( M − λ I ) = 0.
\det(M-\lambda I)=0.
det ( M − λ I ) = 0.
计算得到
det ( M − λ I ) = ∣ 1 − λ 1 1 − λ ∣ = λ 2 − λ − 1.
\det(M-\lambda I)
=
\begin{vmatrix}
1-\lambda&1\\
1&-\lambda
\end{vmatrix}
=
\lambda^2-\lambda-1.
det ( M − λ I ) = 1 − λ 1 1 − λ = λ 2 − λ − 1.
\begin{vmatrix}
1-\lambda&1\
1&-\lambda
\end{vmatrix}
\lambda^2-\lambda-1.
所以 M M M 的两个特征值正是 φ \varphi φ 和 ψ \psi ψ 。
如果把 M M M 对角化成
M = P D P − 1 , D = ( φ 0 0 ψ ) ,
M=PDP^{-1},
\qquad
D=
\begin{pmatrix}
\varphi&0\\
0&\psi
\end{pmatrix},
M = P D P − 1 , D = ( φ 0 0 ψ ) ,
那么
M n = P D n P − 1 = P ( φ n 0 0 ψ n ) P − 1 .
M^n=PD^nP^{-1}
=
P
\begin{pmatrix}
\varphi^n&0\\
0&\psi^n
\end{pmatrix}
P^{-1}.
M n = P D n P − 1 = P ( φ n 0 0 ψ n ) P − 1 .
P
\begin{pmatrix}
\varphi^n&0\
0&\psi^n
\end{pmatrix}
P^{-1}.
Binet 公式就是这个矩阵幂公式展开后的结果。把「反复乘 M M M 」改写成「对特征值取幂」,闭式公式就出现了。
5 \sqrt5 5 在矩阵视角里也有很具体的位置:它是两个特征值的差,
φ − ψ = 5 .
\varphi-\psi=\sqrt5.
φ − ψ = 5 .
求 P − 1 P^{-1} P − 1 时会除以这个差,所以 Binet 公式的分母里出现了 5 \sqrt5 5 。
为什么不用 Binet 公式精确计算大数
理论上,Binet 公式看起来是直接代入:
F n = φ n − ψ n 5 .
F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.
F n = 5 φ n − ψ n .
但对大 n n n 来说,精确计算并不适合这样做。原因有两个。
第一,φ \varphi φ 是无理数,计算机只能用有限精度近似它。双精度浮点数大约只有 15 15 15 到 16 16 16 位有效数字。比如
F 100 = 354224848179261915075 ,
F_{100}=354224848179261915075,
F 100 = 354224848179261915075 ,
已经有 21 21 21 位十进制数字,普通浮点数无法精确表示。
第二,真正输出 F n F_n F n 时,结果本身会很长。即使用闭式公式,结果的位数成本也不会消失。
所以 Binet 公式的主要价值在于理解结构。它告诉我们:
F n F_n F n 的增长速度由 φ n \varphi^n φ n 控制;
5 \sqrt5 5 来自特征方程;
一般线性递推也可以用特征根分析。
实际精确计算大 F n F_n F n ,仍然更适合用快速倍增或矩阵快速幂。它们使用整数运算,时间复杂度是 O ( log n ) O(\log n) O ( log n ) ,不会引入浮点误差。
本章小结
我们想找一个一步到位的公式。假设 F n F_n F n 形如 r n r^n r n ,得到特征方程
r 2 = r + 1.
r^2=r+1.
r 2 = r + 1.
这个方程的两个根是 φ \varphi φ 和 ψ \psi ψ 。用初值确定系数后,得到 Binet 公式
F n = φ n − ψ n 5 .
F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.
F n = 5 φ n − ψ n .
5 \sqrt5 5 出现得很具体:它来自特征方程的判别式,也等于两个特征值的差。下一章我们继续看另一件事:公式里充满无理数,结果为什么总是整数。
第 9 章 5 \sqrt5 5 怎么消失:从无理数回到整数
上一章推出了 Binet 公式:
F n = φ n − ψ n 5 , φ = 1 + 5 2 , ψ = 1 − 5 2 .
F_n=\frac{\varphi^n-\psi^n}{\sqrt5},
\qquad
\varphi=\frac{1+\sqrt5}{2},
\qquad
\psi=\frac{1-\sqrt5}{2}.
F n = 5 φ n − ψ n , φ = 2 1 + 5 , ψ = 2 1 − 5 .
公式里有 5 \sqrt5 5 ,但 F n F_n F n 永远是整数。这一章专门讲这件事:无理数是怎样进来的,又怎样在结果里消失。
先列出 φ \varphi φ 和 ψ \psi ψ 的性质
由定义可得:
φ + ψ = 1 , φ ψ = − 1 , φ − ψ = 5 .
\varphi+\psi=1,
\qquad
\varphi\psi=-1,
\qquad
\varphi-\psi=\sqrt5.
φ + ψ = 1 , φ ψ = − 1 , φ − ψ = 5 .
它们都是方程
r 2 − r − 1 = 0
r^2-r-1=0
r 2 − r − 1 = 0
的根,所以都满足
r 2 = r + 1.
r^2=r+1.
r 2 = r + 1.
这意味着
φ n + 2 = φ n + 1 + φ n , ψ n + 2 = ψ n + 1 + ψ n .
\varphi^{n+2}=\varphi^{n+1}+\varphi^n,
\qquad
\psi^{n+2}=\psi^{n+1}+\psi^n.
φ n + 2 = φ n + 1 + φ n , ψ n + 2 = ψ n + 1 + ψ n .
还有一个很重要的事实:
因此 ψ n \psi^n ψ n 会随着 n n n 增大迅速趋近于 0 0 0 。
比如:
ψ 1 ≈ − 0.618 , ψ 2 ≈ 0.382 , ψ 5 ≈ − 0.090 , ψ 10 ≈ 0.008.
\psi^1\approx-0.618,
\quad
\psi^2\approx0.382,
\quad
\psi^5\approx-0.090,
\quad
\psi^{10}\approx0.008.
ψ 1 ≈ − 0.618 , ψ 2 ≈ 0.382 , ψ 5 ≈ − 0.090 , ψ 10 ≈ 0.008.
二项式展开中的抵消
把 φ n \varphi^n φ n 和 ψ n \psi^n ψ n 展开。先看
φ n = ( 1 + 5 2 ) n = ( 1 + 5 ) n 2 n .
\varphi^n=
\left(\frac{1+\sqrt5}{2}\right)^n
=
\frac{(1+\sqrt5)^n}{2^n}.
φ n = ( 2 1 + 5 ) n = 2 n ( 1 + 5 ) n .
\frac{(1+\sqrt5)^n}{2^n}.
由二项式定理,
( 1 + 5 ) n = ∑ k = 0 n ( n k ) ( 5 ) k .
(1+\sqrt5)^n
=
\sum_{k=0}^{n}\binom{n}{k}(\sqrt5)^k.
( 1 + 5 ) n = k = 0 ∑ n ( k n ) ( 5 ) k .
\sum_{k=0}^{n}\binom{n}{k}(\sqrt5)^k.
当 k k k 是偶数时,( 5 ) k (\sqrt5)^k ( 5 ) k 是整数;当 k k k 是奇数时,( 5 ) k (\sqrt5)^k ( 5 ) k 是 5 \sqrt5 5 的整数倍。
因此可以把展开式写成
( 1 + 5 ) n = A n + B n 5 ,
(1+\sqrt5)^n=A_n+B_n\sqrt5,
( 1 + 5 ) n = A n + B n 5 ,
其中 A n A_n A n 和 B n B_n B n 都是整数。
同理,
( 1 − 5 ) n = A n − B n 5 .
(1-\sqrt5)^n=A_n-B_n\sqrt5.
( 1 − 5 ) n = A n − B n 5 .
两式相减,得到
( 1 + 5 ) n − ( 1 − 5 ) n = 2 B n 5 .
(1+\sqrt5)^n-(1-\sqrt5)^n=2B_n\sqrt5.
( 1 + 5 ) n − ( 1 − 5 ) n = 2 B n 5 .
于是
φ n − ψ n = ( 1 + 5 ) n − ( 1 − 5 ) n 2 n = 2 B n 5 2 n .
\varphi^n-\psi^n
=
\frac{(1+\sqrt5)^n-(1-\sqrt5)^n}{2^n}
=
\frac{2B_n\sqrt5}{2^n}.
φ n − ψ n = 2 n ( 1 + 5 ) n − ( 1 − 5 ) n = 2 n 2 B n 5 .
\frac{(1+\sqrt5)^n-(1-\sqrt5)^n}{2^n}
\frac{2B_n\sqrt5}{2^n}.
代入 B i n e t 公式:
代入 Binet 公式:
代入 B in e t 公式:
代入 Binet 公式:
F n = φ n − ψ n 5 = 2 B n 2 n .
F_n
=
\frac{\varphi^n-\psi^n}{\sqrt5}
=
\frac{2B_n}{2^n}.
F n = 5 φ n − ψ n = 2 n 2 B n .
\frac{\varphi^n-\psi^n}{\sqrt5}
\frac{2B_n}{2^n}.
这个推导说明了为什么 5 \sqrt5 5 会消失:两边共轭项相减后,剩下的部分都带有一个 5 \sqrt5 5 ,正好被分母除掉。
不过这里还留下一个问题:2 B n / 2 n 2B_n/2^n 2 B n / 2 n 看起来只是有理数,为什么一定是整数?最直接的解释是:Binet 公式右侧满足同一个斐波那契递推,而且初值也是 0 0 0 和 1 1 1 。递推定义已经唯一确定了整个数列,所以它给出的每一项就是 F n F_n F n ,必为整数。
用 n = 5 n=5 n = 5 看一次
展开:
( 1 + 5 ) 5 = 1 + 5 5 + 10 ⋅ 5 + 10 ⋅ 5 5 + 5 ⋅ 25 + 25 5 .
(1+\sqrt5)^5
=1+5\sqrt5+10\cdot5+10\cdot5\sqrt5+5\cdot25+25\sqrt5.
( 1 + 5 ) 5 = 1 + 5 5 + 10 ⋅ 5 + 10 ⋅ 5 5 + 5 ⋅ 25 + 25 5 .
整理得
( 1 + 5 ) 5 = 176 + 80 5 .
(1+\sqrt5)^5=176+80\sqrt5.
( 1 + 5 ) 5 = 176 + 80 5 .
同理,
( 1 − 5 ) 5 = 176 − 80 5 .
(1-\sqrt5)^5=176-80\sqrt5.
( 1 − 5 ) 5 = 176 − 80 5 .
相减:
( 1 + 5 ) 5 − ( 1 − 5 ) 5 = 160 5 .
(1+\sqrt5)^5-(1-\sqrt5)^5=160\sqrt5.
( 1 + 5 ) 5 − ( 1 − 5 ) 5 = 160 5 .
所以
F 5 = 1 5 ⋅ 160 5 2 5 = 160 32 = 5.
F_5
=\frac{1}{\sqrt5}\cdot\frac{160\sqrt5}{2^5}
=\frac{160}{32}=5.
F 5 = 5 1 ⋅ 2 5 160 5 = 32 160 = 5.
5 \sqrt5 5 在中间出现,但最后消失了。
F n F_n F n 是最近的整数
Binet 公式还给出一个很有用的近似:
F n = φ n 5 − ψ n 5 .
F_n=\frac{\varphi^n}{\sqrt5}-\frac{\psi^n}{\sqrt5}.
F n = 5 φ n − 5 ψ n .
因为 ∣ ψ ∣ < 1 |\psi|<1 ∣ ψ ∣ < 1 ,当 n ≥ 1 n\ge1 n ≥ 1 时,
∣ ψ n 5 ∣ < 1 5 < 1 2 .
\left|\frac{\psi^n}{\sqrt5}\right|<\frac1{\sqrt5}<\frac12.
5 ψ n < 5 1 < 2 1 .
实际上这个误差还会随着 n n n 增大迅速变小。因此 F n F_n F n 是离
φ n 5
\frac{\varphi^n}{\sqrt5}
5 φ n
最近的整数。
比如:
φ 10 5 ≈ 55.003 , F 10 = 55.
\frac{\varphi^{10}}{\sqrt5}\approx55.003,
\qquad
F_{10}=55.
5 φ 10 ≈ 55.003 , F 10 = 55.
φ 20 5 ≈ 6765.00003 , F 20 = 6765.
\frac{\varphi^{20}}{\sqrt5}\approx6765.00003,
\qquad
F_{20}=6765.
5 φ 20 ≈ 6765.00003 , F 20 = 6765.
矩阵对角化再看一次
第 8 章说过,斐波那契矩阵是
M = ( 1 1 1 0 ) .
M=
\begin{pmatrix}
1&1\\
1&0
\end{pmatrix}.
M = ( 1 1 1 0 ) .
它有两个特征值 φ \varphi φ 和 ψ \psi ψ 。对应的特征向量可以取
( φ 1 ) , ( ψ 1 ) .
\begin{pmatrix}\varphi\\1\end{pmatrix},
\qquad
\begin{pmatrix}\psi\\1\end{pmatrix}.
( φ 1 ) , ( ψ 1 ) .
于是令
P = ( φ ψ 1 1 ) , D = ( φ 0 0 ψ ) .
P=
\begin{pmatrix}
\varphi&\psi\\
1&1
\end{pmatrix},
\qquad
D=
\begin{pmatrix}
\varphi&0\\
0&\psi
\end{pmatrix}.
P = ( φ 1 ψ 1 ) , D = ( φ 0 0 ψ ) .
有
M = P D P − 1 .
M=PDP^{-1}.
M = P D P − 1 .
矩阵 P P P 的逆矩阵是
P − 1 = 1 φ − ψ ( 1 − ψ − 1 φ ) = 1 5 ( 1 − ψ − 1 φ ) .
P^{-1}
=
\frac1{\varphi-\psi}
\begin{pmatrix}
1&-\psi\\
-1&\varphi
\end{pmatrix}
=
\frac1{\sqrt5}
\begin{pmatrix}
1&-\psi\\
-1&\varphi
\end{pmatrix}.
P − 1 = φ − ψ 1 ( 1 − 1 − ψ φ ) = 5 1 ( 1 − 1 − ψ φ ) .
\frac1{\varphi-\psi}
\begin{pmatrix}
1&-\psi\
-1&\varphi
\end{pmatrix}
\frac1{\sqrt5}
\begin{pmatrix}
1&-\psi\
-1&\varphi
\end{pmatrix}.
从初始向量
( 1 0 )
\begin{pmatrix}1\\0\end{pmatrix}
( 1 0 )
出发,有
( F n + 1 F n ) = M n ( 1 0 ) = P D n P − 1 ( 1 0 ) .
\begin{pmatrix}
F_{n+1}\\
F_n
\end{pmatrix}
=M^n
\begin{pmatrix}1\\0\end{pmatrix}
=PD^nP^{-1}
\begin{pmatrix}1\\0\end{pmatrix}.
( F n + 1 F n ) = M n ( 1 0 ) = P D n P − 1 ( 1 0 ) .
先算
P − 1 ( 1 0 ) = 1 5 ( 1 − 1 ) .
P^{-1}
\begin{pmatrix}1\\0\end{pmatrix}
=
\frac1{\sqrt5}
\begin{pmatrix}1\\-1\end{pmatrix}.
P − 1 ( 1 0 ) = 5 1 ( 1 − 1 ) .
\frac1{\sqrt5}
\begin{pmatrix}1\-1\end{pmatrix}.
再乘 D n D^n D n :
D n 1 5 ( 1 − 1 ) = 1 5 ( φ n − ψ n ) .
D^n
\frac1{\sqrt5}
\begin{pmatrix}1\\-1\end{pmatrix}
=
\frac1{\sqrt5}
\begin{pmatrix}\varphi^n\\-\psi^n\end{pmatrix}.
D n 5 1 ( 1 − 1 ) = 5 1 ( φ n − ψ n ) .
\frac1{\sqrt5}
\begin{pmatrix}\varphi^n\-\psi^n\end{pmatrix}.
最后乘 P P P :
P ⋅ 1 5 ( φ n − ψ n ) = 1 5 ( φ n + 1 − ψ n + 1 φ n − ψ n ) .
P\cdot
\frac1{\sqrt5}
\begin{pmatrix}\varphi^n\\-\psi^n\end{pmatrix}
=
\frac1{\sqrt5}
\begin{pmatrix}
\varphi^{n+1}-\psi^{n+1}\\
\varphi^n-\psi^n
\end{pmatrix}.
P ⋅ 5 1 ( φ n − ψ n ) = 5 1 ( φ n + 1 − ψ n + 1 φ n − ψ n ) .
\frac1{\sqrt5}
\begin{pmatrix}
\varphi^{n+1}-\psi^{n+1}\
\varphi^n-\psi^n
\end{pmatrix}.
第二项正是
F n = φ n − ψ n 5 .
F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.
F n = 5 φ n − ψ n .
所以 Binet 公式也可以看成矩阵对角化的结果。反复乘同一个矩阵,在特征向量坐标里变成分别乘 φ \varphi φ 和 ψ \psi ψ 。
为什么无理数会给出整数
现在可以把这件事说清楚。
第一,5 \sqrt5 5 进入公式,是因为特征方程 r 2 − r − 1 = 0 r^2-r-1=0 r 2 − r − 1 = 0 的根包含 5 \sqrt5 5 。
第二,5 \sqrt5 5 在结果中消失,是因为 φ \varphi φ 和 ψ \psi ψ 是一对共轭根。一个含 + 5 +\sqrt5 + 5 ,另一个含 − 5 -\sqrt5 − 5 。Binet 公式取两者的差,刚好留下一个可被分母消去的 5 \sqrt5 5 。
第三,最后得到整数,是因为这个表达式满足斐波那契递推,且初值就是 F 0 = 0 , F 1 = 1 F_0=0,F_1=1 F 0 = 0 , F 1 = 1 。递推把整数性一路传下去。
本章小结
5 \sqrt5 5 不是错误,也不是奇迹。它是代数运算中的中间语言。用它可以把递推拆成两个独立的指数项:
φ n 和 ψ n .
\varphi^n
\quad\text{和}\quad
\psi^n.
φ n 和 ψ n .
两项相减时,无理部分按共轭关系抵消,结果回到整数。下一章我们换一种更具体的语言:用铺砖和走楼梯解释同一个数列。
第 10 章 一个数列也是被数出来的
到上一章为止,我们把 F n F_n F n 从各种角度看过了一遍。递归、算法、矩阵、闭式公式。每一种都把 F n F_n F n 看成"被算出来的数"。
这一章换个角度:F n F_n F n 不只是被算出来的,它还是被"数"出来的。
什么意思?
考虑一段长度为 n 的窄走廊,用 1 格砖和 2 格砖把它铺满。问有多少种铺法。
这个数列是 1, 2, 3, 5, 8, 13, …(n = 1 , 2 , 3 , 4 , 5 , 6 , n = 1, 2, 3, 4, 5, 6, n = 1 , 2 , 3 , 4 , 5 , 6 , …)。它就是斐波那契数列错位一格。也就是说,铺 n 格走廊的方法数等于 F n + 1 F_{n+1} F n + 1 。
这件事本身在第 1 章我们已经看到过。但是这一章要做得更彻底:要让你真的相信"F n F_n F n 在数某个东西",而不只是"恰好等于某个数"。
先动手数
把 n = 1, 2, 3, 4 的铺法都列出来。
n = 1 n = 1 n = 1 ,1 种:
n = 2 n = 2 n = 2 ,2 种:
n = 3 n = 3 n = 3 ,3 种:
n = 4 n = 4 n = 4 ,5 种:
1+1+1+1
1+1+2
1+2+1
2+1+1
2+2
1, 2, 3, 5。和 F 2 F_2 F 2 , F 3 F_3 F 3 , F 4 F_4 F 4 , F 5 F_5 F 5 完全对上。
这些数是怎么来的?是真的能数出来的。n = 4 n = 4 n = 4 的 5 种铺法,每一种都是一种具体的"砖头摆放"。数 5 个具体的摆放,得到 5。
这件事比"算 F 4 = 3 + 2 = 5 F_4=3+2=5 F 4 = 3 + 2 = 5 "更直观。算的时候,5 是一个加法结果。数的时候,5 是一个计数结果。两个 5 是同一个数,但意义不同。
推递推:分最后一块
现在推一下递推。设铺 n n n 格走廊的方法数为 T n T_n T n 。
考虑一条长度为 n n n 的走廊的最后一块砖,也就是贴着走廊右端的那一块。它要么是 1 1 1 格砖,要么是 2 2 2 格砖。
如果最后一块是 1 1 1 格砖,那么前面剩下的是长度 n − 1 n-1 n − 1 的走廊,铺法数等于 T n − 1 T_{n-1} T n − 1 。
如果最后一块是 2 2 2 格砖,那么前面剩下的是长度 n − 2 n-2 n − 2 的走廊,铺法数等于 T n − 2 T_{n-2} T n − 2 。
两种情况不重不漏地覆盖了所有铺法,所以
T n = T n − 1 + T n − 2 .
T_n=T_{n-1}+T_{n-2}.
T n = T n − 1 + T n − 2 .
加上初值 T 1 = 1 , T 2 = 2 T_1=1,T_2=2 T 1 = 1 , T 2 = 2 ,整个数列就定下来了:1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , … 1,2,3,5,8,13,21,34,\ldots 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , … 。
也就是说
T n = F n + 1 .
T_n=F_{n+1}.
T n = F n + 1 .
到这里,斐波那契数列有了一个全新的解释:F n + 1 F_{n+1} F n + 1 是「用 1 1 1 格砖和 2 2 2 格砖铺长度 n n n 走廊的方法数」。
这个解释和代数无关。它不需要递推公式,不需要矩阵,不需要 5 \sqrt5 5 。它直接告诉你 F n F_n F n 是某个具体物体的计数。
为什么这个解释重要
数学里有两种数:抽象的数和具体的数。
抽象的数:F 10 = 55 F_{10} = 55 F 10 = 55 。这个 55 是一个抽象值,它来自一系列加法。
具体的数:长度 9 的走廊有 55 种铺法。这个 55 是一个具体计数,对应到 55 种实实在在的砖头摆放。
同一个 55,意义不同。具体的 55 可以"看",你可以在纸上画出 55 种铺法。抽象的 55 只能"算"。
这种差别在数学里反复出现。组合数学的核心工作之一,就是把抽象的数和具体的计数问题对应起来。一个数列如果能被解释为"某个东西的计数",它就有了组合意义。
斐波那契数列是组合数学最经典的样本之一。它对应很多种"计数对象",铺砖只是其中一种。
再看楼梯
第 1 章见过的:站在 n 阶楼梯下面,每次走 1 阶或 2 阶,问走到顶有多少种走法。
设 W n W_n W n 是走 n n n 阶楼梯的方法数。
n = 1 n = 1 n = 1 :只能一步走完,方法数 1。
n = 2 n = 2 n = 2 :可以一步一步走,也可以一步直接跨 2 阶,方法数 2。
n = 3 n = 3 n = 3 :1+1+1、1+2、2+1,方法数 3。
n = 4 n = 4 n = 4 :1+1+1+1、1+1+2、1+2+1、2+1+1、2+2,方法数 5。
1, 2, 3, 5。W n = F n + 1 W_n=F_{n+1} W n = F n + 1 。
走 n 阶楼梯的方法数,和铺 n 格走廊的方法数,是同一个数。
这不是巧合。两种计数问题有同一个结构:
系统有 n 个"位置"。
每一步占据 1 个或 2 个位置。
问总共多少种"占据方式"。
楼梯和走廊只是"位置"的不同物理外衣。楼梯的位置是阶,走廊的位置是格。但计数结构是一样的。
更精确地说,铺砖和楼梯之间有一个直接的双射:把铺法 1+1+2+2+1 对应到楼梯走法"1 步, 1 步, 2 步, 2 步, 1 步"。每个 1 格砖对应 1 阶,每个 2 格砖对应 2 阶。这种对应是一一的,所以两种计数给出同一个数。
双射是组合数学里很优雅的工具。两个看似无关的集合如果能建立一一对应,它们的元素数就相等。这一点看起来平凡,但是当我们能把一个"难数"的集合对应到一个"好数"的集合时,它就变成了一个强大的工具。
一个细节:索引差一格
注意上面 T 1 = 1 = F 2 T_1=1=F_2 T 1 = 1 = F 2 ,T 2 = 2 = F 3 T_2=2=F_3 T 2 = 2 = F 3 ,……,T n = F n + 1 T_n=F_{n+1} T n = F n + 1 。索引差一格。
为什么差一格?因为我们让 T 0 T_0 T 0 表示「铺长度 0 0 0 的走廊的方法数」。空走廊,一种铺法,什么都不铺。所以 T 0 = 1 = F 1 T_0=1=F_1 T 0 = 1 = F 1 。从这个初值出发,T n = F n + 1 T_n=F_{n+1} T n = F n + 1 。
如果换一种约定,让 T 0 = 0 T_0=0 T 0 = 0 ,也就是「长度 0 0 0 不能铺」,那 T 1 = 1 , T 2 = 1 , T 3 = 2 T_1=1,T_2=1,T_3=2 T 1 = 1 , T 2 = 1 , T 3 = 2 ,……,T n = F n T_n=F_n T n = F n 。索引就对齐了,但 T 2 = 1 T_2=1 T 2 = 1 这种结果就违反直觉,长度 2 2 2 的走廊明明有两种铺法:1 + 1 1+1 1 + 1 和 2 2 2 。
所以「差一格」是更自然的约定。第 1 章里我们说全书统一用 F 0 = 0 , F 1 = 1 F_0=0,F_1=1 F 0 = 0 , F 1 = 1 ,组合计数问题的索引会差一格,错位时用小数字修。这里就是这种修法的具体应用。
一个稍微大一点的例子:n = 6 n=6 n = 6
为了让你相信这件事真的成立,我们数一下 n = 6 n=6 n = 6 的铺法。
按最后一块分类:
最后一块是 1 1 1 格砖:前面是长度 5 5 5 的走廊,有 T 5 = 8 T_5=8 T 5 = 8 种铺法。
最后一块是 2 2 2 格砖:前面是长度 4 4 4 的走廊,有 T 4 = 5 T_4=5 T 4 = 5 种铺法。
总数是
T 6 = T 5 + T 4 = 8 + 5 = 13.
T_6=T_5+T_4=8+5=13.
T 6 = T 5 + T 4 = 8 + 5 = 13.
而 F 7 = 13 F_7=13 F 7 = 13 。对。
这种「分最后一块」的计数方式,比直接列出 13 13 13 种铺法更省事。它利用了递推结构,把大问题拆成两个小问题。但是注意,这里的「拆」是组合意义上的拆,不是代数意义上的拆。我们是在数:T 6 T_6 T 6 的所有铺法,可以分成「由 T 5 T_5 T 5 的铺法后接一块 1 1 1 格砖」和「由 T 4 T_4 T 4 的铺法后接一块 2 2 2 格砖」两类。两类不交,所以数量相加。
这是组合推理和代数推理的一个微妙差别。代数推理关心「等式成立」。组合推理关心「两个集合的元素一一对应」或「一个集合被不重不漏地分成几类」。前者是符号游戏,后者是结构对应。
计数视角带来的新可能
一旦把 F n F_n F n 看成"铺砖方法数",很多事情就变得可以做。
第一,可以反过来证恒等式。比如要证 F 1 F_1 F 1 + F 2 F_2 F 2 + …+ F n = F n + 2 − 1 + F_{n} = F_{n+2} - 1 + F n = F n + 2 − 1 ,可以两边都解释成"某种铺法的数量",然后证明它们数的是同一个集合。这种证明叫组合证明,它比代数证明更"看见在数什么"。第 14 章会专门做这件事。
第二,可以推广到「加权铺砖」。1 1 1 格砖有 c 1 c_1 c 1 种颜色可选,2 2 2 格砖有 c 2 c_2 c 2 种颜色可选,铺法总数满足
T n = c 1 T n − 1 + c 2 T n − 2 .
T_n=c_1T_{n-1}+c_2T_{n-2}.
T n = c 1 T n − 1 + c 2 T n − 2 .
当 c 1 = c 2 = 1 c_1=c_2=1 c 1 = c 2 = 1 时回到斐波那契;当 c 1 = 2 , c 2 = 1 c_1=2,c_2=1 c 1 = 2 , c 2 = 1 时得到 Pell 数列。这种推广让线性递推和加权铺砖对应起来。尾声会展开。
第三,可以把 F n F_n F n 解释成"图上某种结构的数量"。第 12 章会把铺砖推广到路径图上的匹配,看同一个递推在图论里长什么样。
本章小结
斐波那契数列不只是"被算出来的数"。它还是"被数出来的数"。它是某种具体对象的计数。
具体是什么对象?任何具有"一维位置 + 每步占 1 或 2"这种结构的对象。铺砖、楼梯,都是这种结构的不同外衣。
这种组合解释给了我们一个新的视角去看斐波那契数列:
它解释了为什么 F n F_n F n 满足递推 F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} F n = F n − 1 + F n − 2 。因为对应对象的计数满足"第一步占 1 或 2"的分情况。
它给出了一个工具,组合证明,可以用计数来证恒等式,而不仅仅用代数。
它揭示了斐波那契数列的"组合本质":线性、一维、每步 1 或 2。这就是斐波那契数列的组合骨架。
到这里我们已经看到 F n F_n F n 的几种外衣:递归程序、矩阵幂、闭式公式、铺砖计数、楼梯计数。下一章我们会再增加两种外衣:不连续子集和不含相邻 1 的二进制串。看同一个递推怎么藏在更多场景里。
第 11 章 子集与串:同一个对象的四种写法
上一章我们看了铺砖和楼梯,两种计数都给出斐波那契数。这一章再加两种:不含相邻 1 1 1 的二进制串,和不包含连续整数的子集。
四种对象,四种外衣,背后是同一种结构。这一章的重点是建立这四种外衣之间的严格双射,让你看见"它们其实是同一件事"。
先把索引对齐
固定一个整数 N N N 。考虑四类对象:
长度为 N + 1 N+1 N + 1 的走廊铺法。
走 N + 1 N+1 N + 1 阶楼梯、每步走 1 1 1 或 2 2 2 阶的方法。
长度为 N N N 的不含相邻 1 1 1 的二进制串。
集合 { 1 , 2 , … , N } \{1,2,\dots,N\} { 1 , 2 , … , N } 的不含连续整数的子集。
回忆上一章的铺砖计数定义。令 T M T_M T M 表示用 1 1 1 格砖和 2 2 2 格砖铺满长度为 M M M 的走廊的方法数,约定 T 0 = 1 T_0 = 1 T 0 = 1 。于是
T M = T M − 1 + T M − 2 , T 0 = 1 , T 1 = 1 ,
T_M = T_{M-1} + T_{M-2}, \qquad T_0 = 1, T_1 = 1,
T M = T M − 1 + T M − 2 , T 0 = 1 , T 1 = 1 ,
并且
T M = F M + 1 .
T_M = F_{M+1}.
T M = F M + 1 .
四类对象的数量都是 F N + 2 F_{N+2} F N + 2 。这样索引就对齐了。
第一类直接代入:长度 N + 1 N+1 N + 1 走廊的铺法数是 T N + 1 = F N + 2 T_{N+1} = F_{N+2} T N + 1 = F N + 2 。
第二类楼梯和铺砖的对应是显然的:每步走 1 1 1 阶对应 1 1 1 格砖,每步走 2 2 2 阶对应 2 2 2 格砖。走 N + 1 N+1 N + 1 阶楼梯的方法数等于铺 N + 1 N+1 N + 1 格走廊的方法数。
第三、第四类需要稍微推导,下面分别处理。
不含相邻 1 1 1 的二进制串
令 B N B_N B N 表示长度为 N N N 的不含相邻 1 1 1 的二进制串的个数。约定 B 0 = 1 B_0 = 1 B 0 = 1 (空串合法)。
考虑一个长度 N N N 的合法串的最后一位。
如果末位是 0 0 0 :前 N − 1 N-1 N − 1 位可以是任何合法串。方法数 B N − 1 B_{N-1} B N − 1 。
如果末位是 1 1 1 :倒数第二位必须是 0 0 0 (否则出现相邻 1 1 1 ),所以末尾两位是 01 01 01 。前 N − 2 N-2 N − 2 位可以是任何合法串。方法数 B N − 2 B_{N-2} B N − 2 。
所以
B N = B N − 1 + B N − 2 , B 0 = 1 , B 1 = 2.
B_N = B_{N-1} + B_{N-2}, \qquad B_0 = 1, B_1 = 2.
B N = B N − 1 + B N − 2 , B 0 = 1 , B 1 = 2.
初值 B 0 = 1 = F 2 B_0 = 1 = F_2 B 0 = 1 = F 2 ,B 1 = 2 = F 3 B_1 = 2 = F_3 B 1 = 2 = F 3 。所以 B N = F N + 2 B_N = F_{N+2} B N = F N + 2 。
不包含连续整数的子集
令 S N S_N S N 表示 { 1 , 2 , … , N } \{1,2,\dots,N\} { 1 , 2 , … , N } 的不含连续整数的子集的个数。约定 S 0 = 1 S_0 = 1 S 0 = 1 (空集合法)。
考虑元素 N N N 。一个不含连续整数的子集,要么包含 N N N ,要么不包含。
如果包含 N N N :不能包含 N − 1 N-1 N − 1 ,剩下从 { 1 , 2 , … , N − 2 } \{1,2,\dots,N-2\} { 1 , 2 , … , N − 2 } 里选。方法数 S N − 2 S_{N-2} S N − 2 。
如果不包含 N N N :从 { 1 , 2 , … , N − 1 } \{1,2,\dots,N-1\} { 1 , 2 , … , N − 1 } 里选。方法数 S N − 1 S_{N-1} S N − 1 。
所以
S N = S N − 1 + S N − 2 , S 0 = 1 , S 1 = 2.
S_N = S_{N-1} + S_{N-2}, \qquad S_0 = 1, S_1 = 2.
S N = S N − 1 + S N − 2 , S 0 = 1 , S 1 = 2.
初值和 B B B 完全一样,所以 S N = F N + 2 S_N = F_{N+2} S N = F N + 2 。
二进制串和不连续子集的双射
这两类对象数量相等不是巧合,它们之间有一个直接的双射。
给定一个长度 N N N 的二进制串 b 1 b 2 ⋯ b N b_1 b_2 \cdots b_N b 1 b 2 ⋯ b N ,定义子集
S = { i : b i = 1 , 1 ≤ i ≤ N } .
S = \{i : b_i = 1, 1 \le i \le N\}.
S = { i : b i = 1 , 1 ≤ i ≤ N } .
也就是说,第 i i i 位是 1 1 1 ,就把 i i i 放进子集。
这个映射显然是一一对应。串里相邻 1 1 1 等价于子集里连续整数。所以"不含相邻 1 1 1 的长度 N N N 二进制串"和"不含连续整数的 { 1 , … , N } \{1,\dots,N\} { 1 , … , N } 子集"是同一个集合,只是元素写法不同。
铺砖到二进制串:内部边界法
铺砖和二进制串之间的双射不那么显然。我们要用一个严格的方法:内部边界法。
考虑长度为 N + 1 N+1 N + 1 的走廊。它有 N + 1 N+1 N + 1 个格子,编号 1 1 1 到 N + 1 N+1 N + 1 。相邻格子之间有 N N N 条内部边界,分别是 1 ∣ 2 1|2 1∣2 ,2 ∣ 3 2|3 2∣3 ,…,N ∣ N + 1 N|N+1 N ∣ N + 1 。
定义从铺法到二进制串 b 1 b 2 ⋯ b N b_1 b_2 \cdots b_N b 1 b 2 ⋯ b N 的映射:
b i = { 1 , 如果第 i 条内部边界被一块 2 格砖跨过; 0 , 如果第 i 条内部边界是两块砖之间的分界。
b_i =
\begin{cases}
1, & \text{如果第 } i \text{ 条内部边界被一块 } 2 \text{ 格砖跨过;}\\
0, & \text{如果第 } i \text{ 条内部边界是两块砖之间的分界。}
\end{cases}
b i = { 1 , 0 , 如果第 i 条内部边界被一块 2 格砖跨过; 如果第 i 条内部边界是两块砖之间的分界。
为什么这样定义的串不含相邻 1 1 1 ?
如果 b i = 1 b_i = 1 b i = 1 ,说明有一块 2 2 2 格砖盖住第 i i i 格和第 i + 1 i+1 i + 1 格。此时第 i + 1 i+1 i + 1 条边界不可能也被另一块 2 2 2 格砖跨过,因为那会让第 i + 1 i+1 i + 1 格被两块砖同时占用,铺法不合法。所以 b i + 1 b_{i+1} b i + 1 必须是 0 0 0 ,串里不会出现相邻 1 1 1 。
反向映射
给定一个长度 N N N 的不含相邻 1 1 1 的二进制串 b 1 b 2 ⋯ b N b_1 b_2 \cdots b_N b 1 b 2 ⋯ b N ,构造铺法。
对每个 b i = 1 b_i = 1 b i = 1 ,在第 i i i 格和第 i + 1 i+1 i + 1 格上放一块 2 2 2 格砖。因为串里没有相邻 1 1 1 ,这些 2 2 2 格砖不会重叠。所有没有被 2 2 2 格砖覆盖的位置,用 1 1 1 格砖填满。
这就是反向映射。
两个映射互为逆映射。给定铺法走正向映射得到串,再走反向映射得到铺法,结果和原来一样;反过来也成立。所以这是双射。
一个具体例子
用长度 6 6 6 的走廊,即 N = 5 N = 5 N = 5 。考虑铺法:
也就是:第 1 1 1 格一块 1 1 1 格砖,第 2 2 2 、3 3 3 格一块 2 2 2 格砖,第 4 4 4 格一块 1 1 1 格砖,第 5 5 5 、6 6 6 格一块 2 2 2 格砖。
内部边界有 5 5 5 条:1 ∣ 2 1|2 1∣2 ,2 ∣ 3 2|3 2∣3 ,3 ∣ 4 3|4 3∣4 ,4 ∣ 5 4|5 4∣5 ,5 ∣ 6 5|6 5∣6 。
逐条判断:
1 ∣ 2 1|2 1∣2 :第 1 1 1 格和第 2 2 2 格属于不同砖块,所以 b 1 = 0 b_1 = 0 b 1 = 0 。
2 ∣ 3 2|3 2∣3 :第 2 2 2 格和第 3 3 3 格属于同一块 2 2 2 格砖,所以 b 2 = 1 b_2 = 1 b 2 = 1 。
3 ∣ 4 3|4 3∣4 :第 3 3 3 格和第 4 4 4 格属于不同砖块,所以 b 3 = 0 b_3 = 0 b 3 = 0 。
4 ∣ 5 4|5 4∣5 :第 4 4 4 格和第 5 5 5 格属于不同砖块,所以 b 4 = 0 b_4 = 0 b 4 = 0 。
5 ∣ 6 5|6 5∣6 :第 5 5 5 格和第 6 6 6 格属于同一块 2 2 2 格砖,所以 b 5 = 1 b_5 = 1 b 5 = 1 。
得到二进制串 01001 01001 01001 。
反向验证:01001 01001 01001 ,b 2 = 1 b_2 = 1 b 2 = 1 所以放砖盖 2 2 2 、3 3 3 格;b 5 = 1 b_5 = 1 b 5 = 1 所以放砖盖 5 5 5 、6 6 6 格;剩下 1 1 1 、4 4 4 格各放 1 1 1 格砖。还原成原来的铺法。
再走到子集:01001 01001 01001 对应子集 { 2 , 5 } ⊆ { 1 , 2 , 3 , 4 , 5 } \{2, 5\} \subseteq \{1,2,3,4,5\} { 2 , 5 } ⊆ { 1 , 2 , 3 , 4 , 5 } 。2 2 2 和 5 5 5 不连续,符合约束。
四种外衣的总图
把四种对象放在一起:
长度 N+1 的走廊铺法
↕ (砖的长度序列)
走 N+1 阶楼梯的方法
↕ (内部边界法)
长度 N 的不含相邻 1 的二进制串
↕ (位置 = 1)
{1,...,N} 的不含连续整数的子集
四种对象两两之间都能建立双射,所以它们的数量都相等,都是 F N + 2 F_{N+2} F N + 2 。
这是组合数学的力量:通过"换外衣"算数。两个集合如果能建立一一对应,它们的元素数就相等。这一点看起来平凡,但是当我们能把一个"难数"的集合对应到一个"好数"的集合时,它就变成了一个强大的工具。
加权铺砖
四种计数问题都满足同一个递推 X N = X N − 1 + X N − 2 X_N = X_{N-1} + X_{N-2} X N = X N − 1 + X N − 2 ,系数是 1 1 1 和 1 1 1 。如果改系数会怎样?
考虑"加权铺砖":1 1 1 格砖有 c 1 c_1 c 1 种颜色可选,2 2 2 格砖有 c 2 c_2 c 2 种颜色可选。铺法总数 W M W_M W M 满足
W M = c 1 W M − 1 + c 2 W M − 2 , W 0 = 1 , W 1 = c 1 .
W_M = c_1 W_{M-1} + c_2 W_{M-2}, \qquad W_0 = 1, W_1 = c_1.
W M = c 1 W M − 1 + c 2 W M − 2 , W 0 = 1 , W 1 = c 1 .
最后一块砖要么是 1 1 1 格(前面 M − 1 M-1 M − 1 格任意铺,最后 1 1 1 格有 c 1 c_1 c 1 种颜色),要么是 2 2 2 格(前面 M − 2 M-2 M − 2 格任意铺,最后 2 2 2 格有 c 2 c_2 c 2 种颜色)。
c 1 = c 2 = 1 c_1 = c_2 = 1 c 1 = c 2 = 1 回到普通斐波那契铺砖。c 1 = 2 , c 2 = 1 c_1 = 2, c_2 = 1 c 1 = 2 , c 2 = 1 给出 Pell 数列。c 1 = 1 , c 2 = 2 c_1 = 1, c_2 = 2 c 1 = 1 , c 2 = 2 给出 Jacobsthal 数列。任何二阶线性递推都可以这样解释为"加权铺砖"。
这种推广告诉我们:斐波那契数列的"组合本质"是"一维位置 + 每步占 1 1 1 或 2 2 2 ",而任何"一维位置 + 每步占 1 1 1 或 2 2 2 ,每步有可选颜色"的计数问题都对应一个二阶线性递推。这是尾声要展开的主题。
本章小结
四类对象(铺砖、楼梯、二进制串、不连续子集)数量都是 F N + 2 F_{N+2} F N + 2 。这不是巧合,它们之间有严格的双射。
二进制串和不连续子集的双射是显然的:第 i i i 位是 1 1 1 等价于子集包含 i i i 。
铺砖和二进制串的双射通过"内部边界法"建立:每条内部边界是否被 2 2 2 格砖跨过,对应二进制串的一位。这个双射严格、可画图、可逆。
加权铺砖告诉我们:斐波那契是 c 1 = c 2 = 1 c_1 = c_2 = 1 c 1 = c 2 = 1 的特例,任何 c 1 , c 2 c_1, c_2 c 1 , c 2 都对应一种"加权铺砖"问题。这是尾声"一般线性递推"主题的入口。
到这里 F n F_n F n 已经有了五种外衣:递归程序、矩阵幂、闭式公式、铺砖计数、子集与串计数。下一章我们换一种外衣:图。看同一个递推怎么藏在点和线里。
第 12 章 把递推画成图
上一章我们把斐波那契数列从"被算出来的数"换成了"被数出来的数"。铺砖、楼梯、子集、二进制串,四种外衣,同一个递推。
这一章再换一种外衣:图。
图论听起来比组合数学更深奥一点。点和边,路径,匹配,独立集,一堆术语。但是这一章我们尽量不堆术语。要看的事情其实很简单:把一维的"位置序列"画成图上的"点和线",那个递推还在那里。
先画一条最简单的图
考虑 n n n 个点排成一行,相邻两个点之间用一条边连起来。这种图叫"路径图",记作 P n P_n P n 。
P_1:●
P_2:●—●
P_3:●—●—●
P_4:●—●—●—●
P_5:●—●—●—●—●
n 个点,n-1 条边。每条边连接两个相邻的点。
这个图看起来简单到不像图。但是它确实是图,一种最简单的图。后面我们会看到,它的某些计数问题给出斐波那契数。
选一些边,让它们不相邻
考虑这样一个问题:在 P_n 里选一些边,要求选出来的边之间不能共享端点。问有多少种选法。
"不共享端点"的意思是:如果选了边 (1,2),就不能再选 (2,3),因为它们共享点 2。
这种"互不相邻的边的集合",图论里叫"匹配"(matching)。我们这里把它叫"不相邻的边集"就行。
设 M n M_n M n 是 P n P_n P n 里不相邻边集的数量(包括空集)。
n = 2 n=2 n = 2 ,P 2 P_2 P 2 有 1 条边。可选空集或 {边},共 2 种。
n = 3 n=3 n = 3 ,P 3 P_3 P 3 有 2 条边(边 a, b)。可选空集、{a}、{b},不能选 {a, b}(共享中间点),共 3 种。
n = 4 n=4 n = 4 ,P 4 P_4 P 4 有 3 条边(边 a, b, c)。可选 ∅, {a}, {b}, {c}, {a, c},共 5 种({a,b}, {b,c} 不行)。
n = 5 n=5 n = 5 ,P 5 P_5 P 5 有 4 条边。∅, {a}, {b}, {c}, {d}, {a,c}, {a,d}, {b,d},共 8 种。
2, 3, 5, 8。又是斐波那契。M n = F n + 1 M_n=F_{n+1} M n = F n + 1 。
推一下递推。考虑 P_n 的最后一条边,也就是连接点 n − 1 n-1 n − 1 和点 n 的那条边。
如果选这条边,那么不能选它前面的边(连接 n − 2 n-2 n − 2 和 n − 1 n-1 n − 1 的那条),剩下要从 P n − 2 P_{n-2} P n − 2 里选不相邻边集,方法数 M n − 2 M_{n-2} M n − 2 。
如果不选这条边,那么所有边集都是从 P n − 1 P_{n-1} P n − 1 里选的,方法数 M n − 1 M_{n-1} M n − 1 。
所以 M n = M n − 1 + M n − 2 M_n=M_{n-1}+M_{n-2} M n = M n − 1 + M n − 2 。加上初值 M 2 = 2 = F 3 M_2=2=F_3 M 2 = 2 = F 3 ,M 3 = 3 = F 4 M_3=3=F_4 M 3 = 3 = F 4 ,可得 M n = F n + 1 M_n=F_{n+1} M n = F n + 1 。
注意这个递推的形状和铺砖一模一样。"选最后一条边"对应"选最后一块 2 格砖","不选最后一条边"对应"最后一块是 1 格砖"。两种外衣,同一个分情况。
选一些点,让它们不相邻
换个问题。在 P_n 里选一些点,要求选出来的点之间不能相邻(不能直接由一条边连接)。问有多少种选法。
设 I n I_n I n 是 P n P_n P n 里不相邻点集的数量(包括空集)。
n = 1 n = 1 n = 1 :∅, {点1},共 2 种。
n = 2 n = 2 n = 2 :∅, {点1}, {点2},共 3 种(不能同时选 1 和 2)。
n = 3 n = 3 n = 3 :∅, {1}, {2}, {3}, {1,3},共 5 种。
n = 4 n = 4 n = 4 :∅, {1}, {2}, {3}, {4}, {1,3}, {1,4}, {2,4},共 8 种。
2, 3, 5, 8。I n = F n + 2 I_n=F_{n+2} I n = F n + 2 。
递推:考虑最后一个点(点 n)。
如果选点 n n n ,那么不能选点 n − 1 n-1 n − 1 ,剩下从 P n − 2 P_{n-2} P n − 2 里选不相邻点集,方法数 I n − 2 I_{n-2} I n − 2 。
如果不选点 n,那么从 P n − 1 P_{n-1} P n − 1 里选,方法数 I n − 1 I_{n-1} I n − 1 。
所以 I n = I n − 1 + I n − 2 I_n=I_{n-1}+I_{n-2} I n = I n − 1 + I n − 2 ,I 1 = 2 = F 3 I_1=2=F_3 I 1 = 2 = F 3 ,I 2 = 3 = F 4 I_2=3=F_4 I 2 = 3 = F 4 。所以 I n = F n + 2 I_n=F_{n+2} I n = F n + 2 。
这和"选边"问题完全平行。差别还是初值不同,"选点"的初值多了一格,因为"一个点也不选"在某些边界情况下算的方式不同。
这个递推为什么一直出现
到这一步,你应该开始有一种感觉:这个递推 X n = X n − 1 + X n − 2 X_n=X_{n-1}+X_{n-2} X n = X n − 1 + X n − 2 似乎是一种很基础的结构,它会出现在任何"一维位置序列上做局部选择"的计数问题里。
具体来说,下面这些计数问题,全部给出斐波那契数(差一两格索引):
铺砖:长度 n n n 走廊用 1 格和 2 格砖铺满。F n + 1 F_{n+1} F n + 1 。
楼梯:n n n 阶楼梯每次走 1 或 2 阶。F n + 1 F_{n+1} F n + 1 。
不连续子集:{ 1 , … , n } \{1,\ldots,n\} { 1 , … , n } 不含连续整数的子集。F n + 2 F_{n+2} F n + 2 。
二进制串:长度 n n n 不含相邻 1 1 1 的串。F n + 2 F_{n+2} F n + 2 。
路径图匹配:P n P_n P n 不相邻边集。F n + 1 F_{n+1} F n + 1 。
路径图独立集:P n P_n P n 不相邻点集。F n + 2 F_{n+2} F n + 2 。
六种外衣,同一种结构。这种结构可以概括为:
有一串位置。在每个位置上做一个二选一的决定。某些组合是被禁止的,具体来说,相邻两个位置上"激进"的选择不能同时出现。
满足这种结构的计数问题,递推基本都是 X n = X n − 1 + X n − 2 X_n=X_{n-1}+X_{n-2} X n = X n − 1 + X n − 2 。"激进选择"占两个位置,"保守选择"占一个位置。最后一步是激进还是保守,决定了 X n − 2 X_{n-2} X n − 2 还是 X n − 1 X_{n-1} X n − 1 。
这就是斐波那契数列在组合数学里反复出现的真正原因。它对应最简单的一维局部选择结构。
换一种图,递推还在吗
到这里你可能会问:路径图 P n P_n P n 是一个特别简单的图。如果是更复杂的图,递推还在吗?
不一定。换一种图,计数问题会完全不同。
考虑一个 n n n 个点的环(cycle graph, C n C_n C n )。环就是路径图再加上一条边,把首尾连起来。
C_3:●—●—●—●(首尾相连,三角形)
C_4:●—●—●—●—●(首尾相连,四边形)
环上"选不相邻点集"的计数,比路径图上复杂。因为现在首尾也相邻了,"选点 1"和"选点 n"不能同时发生。
设环上不相邻点集数为 I n C I^C_n I n C 。
考虑点 n。
如果选点 n n n :不能选点 1 1 1 和点 n − 1 n-1 n − 1 。剩下要在点 2 , 3 , … , n − 2 2,3,\ldots,n-2 2 , 3 , … , n − 2 之间选不相邻点集,但是这是一个路径图 P n − 3 P_{n-3} P n − 3 ,方法数 I n − 3 I_{n-3} I n − 3 。
如果不选点 n:剩下在点 1, 2, …, n-1 之间选,但这又是一个路径图 P_{n-1},方法数 I n − 1 I_{n-1} I n − 1 。
所以 I n C = I n − 1 + I n − 3 I^C_n=I_{n-1}+I_{n-3} I n C = I n − 1 + I n − 3 。这不是斐波那契递推,它是"跨两格"的递推。
具体值:
I 3 C = 4 I^C_3=4 I 3 C = 4 (∅, {1}, {2}, {3};不能两两同时选)
I 4 C = 7 I^C_4=7 I 4 C = 7 (∅, {1}, {2}, {3}, {4}, {1,3}, {2,4})
I 5 C = 11 I^C_5=11 I 5 C = 11
这不是斐波那契数列。所以"斐波那契递推"不是任何图都给出。它只对应特定结构的图,路径图这种"线性、首尾不连"的结构。
但是即使递推变了,分析思路没变:分最后一步(选点 n 还是不选),把大问题拆成两个小问题。这是组合数学的通用动作。斐波那契只是这种动作在路径图上的具体结果。
一个稍微大一点的问题:星图
考虑 n n n 个点的"星图"。一个中心点,n − 1 n-1 n − 1 个叶子连到中心。
n = 4 n=4 n = 4 的星图:
(中心点连接 3 个叶子)
星图上"不相邻边集"的计数:
每条边都和中心点相连。任意两条边都共享中心点。所以"不相邻边集"只能是空集或只含一条边。
n n n 个点的星图有 n − 1 n-1 n − 1 条边。所以不相邻边集数 = 1 1 1 (空集)+ ( n − 1 ) +(n-1) + ( n − 1 ) (选一条边)= n =n = n 。
n = 4 n=4 n = 4 时是 4 4 4 ,n = 5 n=5 n = 5 时是 5 5 5 ,n = 6 n=6 n = 6 时是 6 6 6 。这是线性函数,不是斐波那契。
所以图的结构变了,计数结果也变了。星图给出线性,路径图给出斐波那契,环图给出更复杂的递推。图的拓扑结构决定了计数问题的难度。
图论作为"形状的语言"
这一章可以收一下了。
图论是一种"形状的语言"。它把"对象之间的关系"画成点和线,让结构变得可视。
斐波那契数列在图论里的位置很特别:它对应最简单的一维图,路径图,上的局部选择计数。这种计数有"第一步占 1 或 2"的局部结构,所以递推是 X n = X n − 1 + X n − 2 X_n=X_{n-1}+X_{n-2} X n = X n − 1 + X n − 2 。
换一种图,递推就变。但是分析方法不变,分最后一步,把大问题拆成小问题。这是组合数学的通用动作。
到这里,斐波那契数列已经有好几种外衣了:
代数外衣:F n = F n − 1 + F n − 2 F_{n} = F_{n-1} + F_{n-2} F n = F n − 1 + F n − 2 的递推解。
算法外衣:朴素递归、记忆化、动态规划、快速倍增、矩阵快速幂。
矩阵外衣:M n M^n M n 的矩阵元素。
闭式外衣:( φ n − ψ n ) / 5 (\varphi^n-\psi^n)/\sqrt5 ( φ n − ψ n ) / 5 。
组合外衣:铺砖、楼梯、子集、二进制串。
图论外衣:路径图上的匹配和独立集。
每一种外衣都给出同一个数列。每一种外衣都强调了斐波那契数列的某一种性质:代数强调递推,算法强调计算,矩阵强调线性变换,闭式强调增长,组合强调计数,图论强调形状。
接下来几章会换到更深的视角,整数性质和模周期。每一种视角都会给斐波那契数列涂上一层新的颜色。
但是结构还是那个结构。
第 13 章 恒等式的宇宙:代数证法
到这一章为止,我们见过了斐波那契数列的几种外衣:递归程序、矩阵幂、闭式公式、铺砖计数、图上匹配。每一种外衣都给出了同一个数列,但给出的方式不同。
这一章和下一章要看另一件事:斐波那契数之间的恒等式。
恒等式是把几个斐波那契数连起来的等式。比如
F 2 n = F n ( 2 F n + 1 − F n ) .
F_{2n}=F_n(2F_{n+1}-F_n).
F 2 n = F n ( 2 F n + 1 − F n ) .
这种等式在斐波那契数列里有很多条,每一条都可以用几种不同方式证明。这一章讲代数证法,下一章讲组合证法。两章用同一组恒等式,方便比较两种证法的差别。
先列五个恒等式
我们挑五个有代表性的恒等式,作为两章的样本。
恒等式 1:加和公式
∑ i = 1 n F i = F n + 2 − 1.
\sum_{i=1}^{n}F_i=F_{n+2}-1.
i = 1 ∑ n F i = F n + 2 − 1.
比如 n = 4 n=4 n = 4 时,
F 1 + F 2 + F 3 + F 4 = 1 + 1 + 2 + 3 = 7 ,
F_1+F_2+F_3+F_4=1+1+2+3=7,
F 1 + F 2 + F 3 + F 4 = 1 + 1 + 2 + 3 = 7 ,
而
F 6 − 1 = 8 − 1 = 7.
F_6-1=8-1=7.
F 6 − 1 = 8 − 1 = 7.
恒等式 2:Cassini 恒等式
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
F_{n+1}F_{n-1}-F_n^2=(-1)^n.
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
比如 n = 4 n=4 n = 4 时,
F 5 F 3 − F 4 2 = 5 ⋅ 2 − 9 = 1 = ( − 1 ) 4 .
F_5F_3-F_4^2=5\cdot2-9=1=(-1)^4.
F 5 F 3 − F 4 2 = 5 ⋅ 2 − 9 = 1 = ( − 1 ) 4 .
n = 5 n=5 n = 5 时,
F 6 F 4 − F 5 2 = 8 ⋅ 3 − 25 = − 1 = ( − 1 ) 5 .
F_6F_4-F_5^2=8\cdot3-25=-1=(-1)^5.
F 6 F 4 − F 5 2 = 8 ⋅ 3 − 25 = − 1 = ( − 1 ) 5 .
这条恒等式的右边随奇偶变化,在斐波那契恒等式里很有辨识度。
恒等式 3:翻倍公式
F 2 n = F n ( 2 F n + 1 − F n ) .
F_{2n}=F_n(2F_{n+1}-F_n).
F 2 n = F n ( 2 F n + 1 − F n ) .
比如 n = 5 n=5 n = 5 时,
F 10 = 55 ,
F_{10}=55,
F 10 = 55 ,
而
F 5 ( 2 F 6 − F 5 ) = 5 ( 16 − 5 ) = 55.
F_5(2F_6-F_5)=5(16-5)=55.
F 5 ( 2 F 6 − F 5 ) = 5 ( 16 − 5 ) = 55.
恒等式 4:加法公式
F n + m = F n + 1 F m + F n F m − 1 .
F_{n+m}=F_{n+1}F_m+F_nF_{m-1}.
F n + m = F n + 1 F m + F n F m − 1 .
比如 n = 3 , m = 4 n=3,m=4 n = 3 , m = 4 时,
而
F 4 F 4 + F 3 F 3 = 3 ⋅ 3 + 2 ⋅ 2 = 13.
F_4F_4+F_3F_3=3\cdot3+2\cdot2=13.
F 4 F 4 + F 3 F 3 = 3 ⋅ 3 + 2 ⋅ 2 = 13.
恒等式 5:平方和公式
∑ i = 1 n F i 2 = F n F n + 1 .
\sum_{i=1}^{n}F_i^2=F_nF_{n+1}.
i = 1 ∑ n F i 2 = F n F n + 1 .
比如 n = 4 n=4 n = 4 时,
1 2 + 1 2 + 2 2 + 3 2 = 15 ,
1^2+1^2+2^2+3^2=15,
1 2 + 1 2 + 2 2 + 3 2 = 15 ,
而
F 4 F 5 = 3 ⋅ 5 = 15.
F_4F_5=3\cdot5=15.
F 4 F 5 = 3 ⋅ 5 = 15.
这五条恒等式各有特点。加和公式把一串求和化成一个斐波那契数。Cassini 把乘积和平方的差化成 ± 1 \pm1 ± 1 。翻倍公式把 F 2 n F_{2n} F 2 n 化成 F n F_n F n 和 F n + 1 F_{n+1} F n + 1 的组合。加法公式把 F n + m F_{n+m} F n + m 拆成关于 n n n 和 m m m 的乘积。平方和公式把一串平方和化成两个相邻斐波那契数的乘积。
代数证法 1:加和公式
要证
∑ i = 1 n F i = F n + 2 − 1.
\sum_{i=1}^{n}F_i=F_{n+2}-1.
i = 1 ∑ n F i = F n + 2 − 1.
对 n n n 归纳。
当 n = 1 n=1 n = 1 时,
F 1 = 1 = F 3 − 1.
F_1=1=F_3-1.
F 1 = 1 = F 3 − 1.
假设对 n n n 成立,即
F 1 + F 2 + ⋯ + F n = F n + 2 − 1.
F_1+F_2+\cdots+F_n=F_{n+2}-1.
F 1 + F 2 + ⋯ + F n = F n + 2 − 1.
那么
F 1 + F 2 + ⋯ + F n + F n + 1 = ( F n + 2 − 1 ) + F n + 1 = F n + 3 − 1.
\begin{aligned}
F_1+F_2+\cdots+F_n+F_{n+1}
&=(F_{n+2}-1)+F_{n+1}\\
&=F_{n+3}-1.
\end{aligned}
F 1 + F 2 + ⋯ + F n + F n + 1 = ( F n + 2 − 1 ) + F n + 1 = F n + 3 − 1.
所以对 n + 1 n+1 n + 1 也成立。归纳完成。
这个证法很干净,但它没有说明这个等式在数什么。
代数证法 2:Cassini 恒等式
要证
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
F_{n+1}F_{n-1}-F_n^2=(-1)^n.
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
用归纳法。
当 n = 1 n=1 n = 1 时,
F 2 F 0 − F 1 2 = 1 ⋅ 0 − 1 = − 1 = ( − 1 ) 1 .
F_2F_0-F_1^2=1\cdot0-1=-1=(-1)^1.
F 2 F 0 − F 1 2 = 1 ⋅ 0 − 1 = − 1 = ( − 1 ) 1 .
当 n = 2 n=2 n = 2 时,
F 3 F 1 − F 2 2 = 2 ⋅ 1 − 1 = 1 = ( − 1 ) 2 .
F_3F_1-F_2^2=2\cdot1-1=1=(-1)^2.
F 3 F 1 − F 2 2 = 2 ⋅ 1 − 1 = 1 = ( − 1 ) 2 .
假设对 n n n 成立。计算 n + 1 n+1 n + 1 的左边:
F n + 2 F n − F n + 1 2 = ( F n + 1 + F n ) F n − F n + 1 2 = F n + 1 F n + F n 2 − F n + 1 2 = F n + 1 ( F n − F n + 1 ) + F n 2 = − F n + 1 F n − 1 + F n 2 = − ( F n + 1 F n − 1 − F n 2 ) = − ( − 1 ) n = ( − 1 ) n + 1 .
\begin{aligned}
F_{n+2}F_n-F_{n+1}^2
&=(F_{n+1}+F_n)F_n-F_{n+1}^2\\
&=F_{n+1}F_n+F_n^2-F_{n+1}^2\\
&=F_{n+1}(F_n-F_{n+1})+F_n^2\\
&=-F_{n+1}F_{n-1}+F_n^2\\
&=-(F_{n+1}F_{n-1}-F_n^2)\\
&=-(-1)^n\\
&=(-1)^{n+1}.
\end{aligned}
F n + 2 F n − F n + 1 2 = ( F n + 1 + F n ) F n − F n + 1 2 = F n + 1 F n + F n 2 − F n + 1 2 = F n + 1 ( F n − F n + 1 ) + F n 2 = − F n + 1 F n − 1 + F n 2 = − ( F n + 1 F n − 1 − F n 2 ) = − ( − 1 ) n = ( − 1 ) n + 1 .
所以对 n + 1 n+1 n + 1 也成立。
这个证明的关键是符号翻转。每推进一项,右边乘上一个 − 1 -1 − 1 ,于是奇偶性出现了。
代数证法 3:翻倍公式
要证
F 2 n = F n ( 2 F n + 1 − F n ) .
F_{2n}=F_n(2F_{n+1}-F_n).
F 2 n = F n ( 2 F n + 1 − F n ) .
从加法公式出发:
F n + m = F n + 1 F m + F n F m − 1 .
F_{n+m}=F_{n+1}F_m+F_nF_{m-1}.
F n + m = F n + 1 F m + F n F m − 1 .
令 m = n m=n m = n ,得到
F 2 n = F n + 1 F n + F n F n − 1 .
F_{2n}=F_{n+1}F_n+F_nF_{n-1}.
F 2 n = F n + 1 F n + F n F n − 1 .
提出 F n F_n F n :
F 2 n = F n ( F n + 1 + F n − 1 ) .
F_{2n}=F_n(F_{n+1}+F_{n-1}).
F 2 n = F n ( F n + 1 + F n − 1 ) .
又因为
F n − 1 = F n + 1 − F n ,
F_{n-1}=F_{n+1}-F_n,
F n − 1 = F n + 1 − F n ,
所以
F 2 n = F n ( 2 F n + 1 − F n ) .
F_{2n}=F_n(2F_{n+1}-F_n).
F 2 n = F n ( 2 F n + 1 − F n ) .
一旦有了加法公式,翻倍公式就是它的特例。
代数证法 4:加法公式
要证
F n + m = F n + 1 F m + F n F m − 1 .
F_{n+m}=F_{n+1}F_m+F_nF_{m-1}.
F n + m = F n + 1 F m + F n F m − 1 .
固定 m m m ,对 n n n 归纳。
当 n = 0 n=0 n = 0 时,
F m = F 1 F m + F 0 F m − 1 .
F_m=F_1F_m+F_0F_{m-1}.
F m = F 1 F m + F 0 F m − 1 .
当 n = 1 n=1 n = 1 时,
F m + 1 = F 2 F m + F 1 F m − 1 = F m + F m − 1 .
F_{m+1}=F_2F_m+F_1F_{m-1}=F_m+F_{m-1}.
F m + 1 = F 2 F m + F 1 F m − 1 = F m + F m − 1 .
假设公式对 n n n 和 n − 1 n-1 n − 1 都成立。则
F n + 1 + m = F n + m + F n + m − 1 = ( F n + 1 F m + F n F m − 1 ) + ( F n F m + F n − 1 F m − 1 ) = ( F n + 1 + F n ) F m + ( F n + F n − 1 ) F m − 1 = F n + 2 F m + F n + 1 F m − 1 .
\begin{aligned}
F_{n+1+m}
&=F_{n+m}+F_{n+m-1}\\
&=(F_{n+1}F_m+F_nF_{m-1})+(F_nF_m+F_{n-1}F_{m-1})\\
&=(F_{n+1}+F_n)F_m+(F_n+F_{n-1})F_{m-1}\\
&=F_{n+2}F_m+F_{n+1}F_{m-1}.
\end{aligned}
F n + 1 + m = F n + m + F n + m − 1 = ( F n + 1 F m + F n F m − 1 ) + ( F n F m + F n − 1 F m − 1 ) = ( F n + 1 + F n ) F m + ( F n + F n − 1 ) F m − 1 = F n + 2 F m + F n + 1 F m − 1 .
这正是加法公式对 n + 1 n+1 n + 1 的形式。归纳完成。
这里用的是二阶归纳,因为斐波那契递推本身也是二阶的。
代数证法 5:平方和公式
要证
∑ i = 1 n F i 2 = F n F n + 1 .
\sum_{i=1}^{n}F_i^2=F_nF_{n+1}.
i = 1 ∑ n F i 2 = F n F n + 1 .
对 n n n 归纳。
当 n = 1 n=1 n = 1 时,
F 1 2 = 1 = F 1 F 2 .
F_1^2=1=F_1F_2.
F 1 2 = 1 = F 1 F 2 .
假设对 n n n 成立。则
F 1 2 + F 2 2 + ⋯ + F n 2 + F n + 1 2 = F n F n + 1 + F n + 1 2 = F n + 1 ( F n + F n + 1 ) = F n + 1 F n + 2 .
\begin{aligned}
F_1^2+F_2^2+\cdots+F_n^2+F_{n+1}^2
&=F_nF_{n+1}+F_{n+1}^2\\
&=F_{n+1}(F_n+F_{n+1})\\
&=F_{n+1}F_{n+2}.
\end{aligned}
F 1 2 + F 2 2 + ⋯ + F n 2 + F n + 1 2 = F n F n + 1 + F n + 1 2 = F n + 1 ( F n + F n + 1 ) = F n + 1 F n + 2 .
所以对 n + 1 n+1 n + 1 也成立。
代数证法的味道
回头看五个证明,它们有共同特点:每一步都是符号操作,不依赖图像,也不依赖具体计数对象。证明完成后,我们知道等式成立,但不一定知道等式在说什么。
代数证法的优点是严谨、机械、可执行。给定一个恒等式,只要找到归纳思路,通常就能推下去。
它的局限也很清楚。Cassini 恒等式为什么差一个 ± 1 \pm1 ± 1 ?翻倍公式为什么出现 2 F n + 1 − F n 2F_{n+1}-F_n 2 F n + 1 − F n ?平方和为什么会变成 F n F n + 1 F_nF_{n+1} F n F n + 1 ?代数证法能证明等式成立,却不一定能解释这些结构从哪里来。
一个关于矩阵的注记
矩阵证法属于代数证法的一个紧凑版本。第 7 章我们看到:
M n = ( F n + 1 F n F n F n − 1 ) .
M^n=
\begin{pmatrix}
F_{n+1}&F_n\\
F_n&F_{n-1}
\end{pmatrix}.
M n = ( F n + 1 F n F n F n − 1 ) .
于是
M n M m = M n + m
M^nM^m=M^{n+m}
M n M m = M n + m
给出加法公式;
det ( M n ) = det ( M ) n
\det(M^n)=\det(M)^n
det ( M n ) = det ( M ) n
给出 Cassini 恒等式;
M 2 n = ( M n ) 2
M^{2n}=(M^n)^2
M 2 n = ( M n ) 2
给出翻倍公式。
矩阵证法比逐项归纳更紧凑。一个矩阵等式能同时推出多条恒等式。但它仍然属于符号操作。它能告诉我们等式成立,却不一定告诉我们等式在数什么。
要看等式在数什么,就要换到组合证法。
本章小结
这一章用代数方法证明了几条斐波那契恒等式。代数证法的特点是严谨、直接、可执行;它的不足是解释性不够强。
下一章会用组合证法重证其中几条适合计数解释的恒等式。加和公式、加法公式、翻倍公式、平方和公式都能解释成具体铺法的计数。Cassini 恒等式则更适合矩阵语言,这也提醒我们:不同数学事实有不同的自然语言。
第 14 章 恒等式在数什么:组合证法与它的边界
上一章我们用代数证法证了五条斐波那契恒等式。每条都成立,但每条都看不出"在数什么"。
这一章换一种证法。挑出适合计数解释的恒等式,找一个具体的计数对象,让等式两边用两种方式数同一批对象。这种证法叫组合证法。
但是有一条恒等式我们不在这一章硬证。Cassini 恒等式的自然语言是矩阵行列式,强行写成不完整的组合证明反而会削弱可信度。本章会承认这一点。
组合证法要做什么
组合证法的核心动作是:找一个集合 S S S ,让等式两边用两种方式数 S S S 的元素。
左边把 S S S 按"某种特征"分类,得到一个表达式。
右边把 S S S 按"另一种特征"分类,得到另一个表达式。
两种分类数的是同一个集合,所以两个表达式相等。
这听起来抽象,下面用铺砖做具体的演示。我们的计数对象是走廊铺法。令 T M T_M T M 表示用 1 1 1 格砖和 2 2 2 格砖铺满长度 M M M 走廊的方法数,约定 T 0 = 1 T_0 = 1 T 0 = 1 。我们已经知道
T M = F M + 1 .
T_M = F_{M+1}.
T M = F M + 1 .
证明一:加和公式
证明
∑ i = 1 n F i = F n + 2 − 1.
\sum_{i=1}^{n} F_i = F_{n+2} - 1.
i = 1 ∑ n F i = F n + 2 − 1.
考虑长度 n + 1 n+1 n + 1 的走廊铺法。它的总数是 T n + 1 = F n + 2 T_{n+1} = F_{n+2} T n + 1 = F n + 2 。
把这些铺法分成两类。
第一类:全 1 1 1 格砖的铺法。这一类只有 1 1 1 种,整条走廊都被 1 1 1 格砖铺满。
第二类:至少有一块 2 2 2 格砖的铺法。对这一类,按"最后一块 2 2 2 格砖的位置"分类。
设最后一块 2 2 2 格砖占据第 k k k 格和第 k + 1 k+1 k + 1 格,其中 1 ≤ k ≤ n 1 \le k \le n 1 ≤ k ≤ n 。这块砖的右边没有别的 2 2 2 格砖,所以右边全是 1 1 1 格砖,唯一一种。这块砖的左边长度 k − 1 k-1 k − 1 ,可以任意铺,方法数 T k − 1 = F k T_{k-1} = F_k T k − 1 = F k 。
把 k k k 从 1 1 1 到 n n n 加起来,第二类的总数是
∑ k = 1 n F k .
\sum_{k=1}^{n} F_k.
k = 1 ∑ n F k .
两类相加等于总数:
F n + 2 = 1 + ∑ k = 1 n F k .
F_{n+2}=1+\sum_{k=1}^{n}F_k.
F n + 2 = 1 + k = 1 ∑ n F k .
移项得到
∑ k = 1 n F k = F n + 2 − 1.
\sum_{k=1}^{n} F_k = F_{n+2} - 1.
k = 1 ∑ n F k = F n + 2 − 1.
这就证明了加和公式。组合证法告诉我们:等式右边 F n + 2 − 1 F_{n+2} - 1 F n + 2 − 1 数的是"长度 n + 1 n+1 n + 1 走廊里至少有一块 2 2 2 格砖的铺法",左边 ∑ F k \sum F_k ∑ F k 是按"最后一块 2 2 2 格砖位置"分类的总数。两边数同一个集合。
证明二:加法公式
证明
F n + m = F n + 1 F m + F n F m − 1 .
F_{n+m} = F_{n+1} F_m + F_n F_{m-1}.
F n + m = F n + 1 F m + F n F m − 1 .
下面先假设 n ≥ 2 , m ≥ 2 n \ge 2, m \ge 2 n ≥ 2 , m ≥ 2 。当 n = 1 n = 1 n = 1 或 m = 1 m = 1 m = 1 时,公式可直接代入验证。
考虑长度 n + m − 1 n+m-1 n + m − 1 的走廊铺法。它的总数是 T n + m − 1 = F n + m T_{n+m-1} = F_{n+m} T n + m − 1 = F n + m 。
观察第 n n n 格。这一格的砖有三种状态。
情况一 :第 n n n 格是一块 1 1 1 格砖。
左边长度 n − 1 n-1 n − 1 ,任意铺,方法数 T n − 1 = F n T_{n-1} = F_n T n − 1 = F n 。
右边长度 m − 1 m-1 m − 1 ,任意铺,方法数 T m − 1 = F m T_{m-1} = F_m T m − 1 = F m 。
这一类总数 F n F m F_n F_m F n F m 。
情况二 :第 n − 1 n-1 n − 1 格和第 n n n 格被同一块 2 2 2 格砖覆盖。
左边长度 n − 2 n-2 n − 2 ,任意铺,方法数 T n − 2 = F n − 1 T_{n-2} = F_{n-1} T n − 2 = F n − 1 。
右边长度 m − 1 m-1 m − 1 ,任意铺,方法数 T m − 1 = F m T_{m-1} = F_m T m − 1 = F m 。
这一类总数 F n − 1 F m F_{n-1} F_m F n − 1 F m 。
情况三 :第 n n n 格和第 n + 1 n+1 n + 1 格被同一块 2 2 2 格砖覆盖。
左边长度 n − 1 n-1 n − 1 ,任意铺,方法数 T n − 1 = F n T_{n-1} = F_n T n − 1 = F n 。
右边长度 m − 2 m-2 m − 2 ,任意铺,方法数 T m − 2 = F m − 1 T_{m-2} = F_{m-1} T m − 2 = F m − 1 。
这一类总数 F n F m − 1 F_n F_{m-1} F n F m − 1 。
三种情况不重不漏,所以
F n + m = F n F m + F n − 1 F m + F n F m − 1 = ( F n + F n − 1 ) F m + F n F m − 1 = F n + 1 F m + F n F m − 1 .
\begin{aligned}
F_{n+m}
&=F_nF_m+F_{n-1}F_m+F_nF_{m-1}\\
&=(F_n+F_{n-1})F_m+F_nF_{m-1}\\
&=F_{n+1}F_m+F_nF_{m-1}.
\end{aligned}
F n + m = F n F m + F n − 1 F m + F n F m − 1 = ( F n + F n − 1 ) F m + F n F m − 1 = F n + 1 F m + F n F m − 1 .
最后一步用了斐波那契递推 F n + 1 = F n + F n − 1 F_{n+1} = F_n + F_{n-1} F n + 1 = F n + F n − 1 。
这就证明了加法公式。组合证法告诉我们:等式数的是"长度 n + m − 1 n+m-1 n + m − 1 走廊的铺法",按"第 n n n 格的砖的状态"分成三类。
证明三:翻倍公式
翻倍公式不必单独再铺一遍,可以作为加法公式的直接结果。
令 m = n m = n m = n ,加法公式给出
F 2 n = F n + 1 F n + F n F n − 1 .
F_{2n}=F_{n+1}F_n+F_nF_{n-1}.
F 2 n = F n + 1 F n + F n F n − 1 .
提取公因子 F n F_n F n :
F 2 n = F n ( F n + 1 + F n − 1 ) .
F_{2n}=F_n(F_{n+1}+F_{n-1}).
F 2 n = F n ( F n + 1 + F n − 1 ) .
用恒等式 F n + 1 + F n − 1 = 2 F n + 1 − F n F_{n+1} + F_{n-1} = 2 F_{n+1} - F_n F n + 1 + F n − 1 = 2 F n + 1 − F n (这是 F n − 1 = F n + 1 − F n F_{n-1} = F_{n+1} - F_n F n − 1 = F n + 1 − F n 的直接推论),得到
F 2 n = F n ( 2 F n + 1 − F n ) .
F_{2n}=F_n(2F_{n+1}-F_n).
F 2 n = F n ( 2 F n + 1 − F n ) .
这一节的要点是:组合证法已经解释了加法公式在数什么,翻倍公式只是把切点放在正中间,即 m = n m = n m = n 。
从计数角度看,翻倍公式数的是"长度 2 n − 1 2n-1 2 n − 1 走廊的铺法",按"中间位置 n n n 的砖的状态"分类。三类相加给出 F n 2 + 2 F n F n − 1 F_n^2 + 2 F_n F_{n-1} F n 2 + 2 F n F n − 1 ,化简后就是 F n ( 2 F n + 1 − F n ) F_n (2 F_{n+1} - F_n) F n ( 2 F n + 1 − F n ) 。
证明四:平方和公式
证明
∑ i = 1 n F i 2 = F n F n + 1 .
\sum_{i=1}^{n} F_i^2 = F_n F_{n+1}.
i = 1 ∑ n F i 2 = F n F n + 1 .
这个证明需要一个新的计数对象:两条走廊的铺法对。
考虑两条走廊:上面一条长度 n n n ,下面一条长度 n − 1 n-1 n − 1 。两条走廊左端对齐。我们数的是"上走廊的铺法 + 下走廊的铺法"这种有序对,总数是
T n ⋅ T n − 1 = F n + 1 ⋅ F n .
T_n \cdot T_{n-1} = F_{n+1} \cdot F_n.
T n ⋅ T n − 1 = F n + 1 ⋅ F n .
这就是等式右边。
现在用另一种方式数同一批铺法对。
定义"共同边界":某个位置 j j j 同时是上走廊铺法和下走廊铺法的砖块分界。j = 0 j = 0 j = 0 一定是共同边界(两条走廊的左端点)。
取最后一个共同边界,设它在位置 j j j ,其中 0 ≤ j ≤ n − 1 0 \le j \le n-1 0 ≤ j ≤ n − 1 。
左边 :从位置 0 0 0 到位置 j j j ,两条走廊长度都是 j j j ,可以任意铺。左边铺法对的数量是 T j ⋅ T j = T j 2 = F j + 1 2 T_j \cdot T_j = T_j^2 = F_{j+1}^2 T j ⋅ T j = T j 2 = F j + 1 2 。
右边 :从位置 j j j 到右端,两条走廊的剩余长度分别为 a = n − j a = n - j a = n − j 和 a − 1 = n − 1 − j a - 1 = n - 1 - j a − 1 = n − 1 − j ,两者长度差 1 1 1 。如果右边不再出现新的共同边界,那么两条走廊的砖块边界必须彼此错开。这个条件会强制右边铺法唯一。
当 a a a 为奇数时,较长的走廊先放一块 1 1 1 格砖,之后全放 2 2 2 格砖;较短的走廊全放 2 2 2 格砖。
当 a a a 为偶数时,较长的走廊全放 2 2 2 格砖;较短的走廊先放一块 1 1 1 格砖,之后全放 2 2 2 格砖。
因此,最后共同边界一旦确定,右边没有选择,左边两条长度相同的走廊可以任意铺。
所以最后共同边界在位置 j j j 的铺法对数量是 F j + 1 2 F_{j+1}^2 F j + 1 2 。
把 j j j 从 0 0 0 到 n − 1 n-1 n − 1 加起来,覆盖所有铺法对:
F n F n + 1 = ∑ j = 0 n − 1 F j + 1 2 = ∑ i = 1 n F i 2 .
F_nF_{n+1}=\sum_{j=0}^{n-1}F_{j+1}^2=\sum_{i=1}^{n}F_i^2.
F n F n + 1 = j = 0 ∑ n − 1 F j + 1 2 = i = 1 ∑ n F i 2 .
这就证明了平方和公式。
建议读者画 n = 5 n = 5 n = 5 的例子:上走廊长度 5 5 5 ,下走廊长度 4 4 4 。标出最后共同边界的位置,验证右边的"交替"模式。
Cassini 恒等式为什么不在这里硬证
Cassini 恒等式是
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
F_{n+1}F_{n-1}-F_n^2=(-1)^n.
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
这条恒等式涉及两个乘积的差,组合证法需要"符号反转双射",对本书当前读者来说不够自然。强行写成不完整的组合证明反而会让读者困惑。
它更自然的语言是矩阵行列式。回忆第 7 章的矩阵
M = ( 1 1 1 0 ) ,
M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix},
M = ( 1 1 1 0 ) ,
以及它的幂
M n = ( F n + 1 F n F n F n − 1 ) .
M^n=\begin{pmatrix}F_{n+1}&F_n\\F_n&F_{n-1}\end{pmatrix}.
M n = ( F n + 1 F n F n F n − 1 ) .
行列式满足 det ( M n ) = det ( M ) n \det(M^n) = \det(M)^n det ( M n ) = det ( M ) n 。计算两边:
det ( M n ) = F n + 1 F n − 1 − F n 2 ,
\det(M^n) = F_{n+1} F_{n-1} - F_n^2,
det ( M n ) = F n + 1 F n − 1 − F n 2 ,
det ( M ) n = ( − 1 ) n .
\det(M)^n = (-1)^n.
det ( M ) n = ( − 1 ) n .
所以
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
F_{n+1}F_{n-1}-F_n^2=(-1)^n.
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
这不是组合证法,但它给读者一个重要判断:不同恒等式有不同的自然语言。Cassini 恒等式的自然语言是矩阵行列式,而不是计数。
组合证法的分工
这一章做了什么?
加和公式、加法公式、翻倍公式、平方和公式,四条恒等式都被解释成具体的铺法计数。每条等式的两边数同一个集合,只是分类方式不同。
Cassini 恒等式不适合硬做组合证法,它的自然语言是矩阵行列式。这不丢脸,反而是一个判断:不是每一种数学事实都应该被强行翻译成同一种语言。
组合证法的价值不在于替代代数证法,而在于回答"这个等式到底在数什么"。代数证法告诉你等式成立,组合证法告诉你等式在数什么。两种证法都做完,理解才完整。
下一章我们换一种语言:生成函数。把整个数列压成一个对象,用对象之间的运算表示数列之间的运算。这是另一种"换语言"的样本。
第 15 章 生成函数:把数列压成一个对象
到上一章为止,我们处理斐波那契数列的方式大多是逐项处理:算 F 5 F_5 F 5 ,证 F 2 n F_{2n} F 2 n ,推 F n m o d 10 F_n\bmod10 F n mod 10 的周期。每次都在处理一个值、一对值,或者一个具体恒等式。
这一章换一种思路:能不能把整个数列
F 0 , F 1 , F 2 , F 3 , …
F_0,F_1,F_2,F_3,\ldots
F 0 , F 1 , F 2 , F 3 , …
当成一个对象来处理?
答案是:可以。工具叫生成函数。
把数列编码成幂级数
考虑无穷级数
G ( x ) = F 0 + F 1 x + F 2 x 2 + F 3 x 3 + ⋯ .
G(x)=F_0+F_1x+F_2x^2+F_3x^3+\cdots.
G ( x ) = F 0 + F 1 x + F 2 x 2 + F 3 x 3 + ⋯ .
把斐波那契数列的每一项当作系数,就得到一个关于 x x x 的幂级数。这个 G ( x ) G(x) G ( x ) 就是斐波那契数列的生成函数。
这里暂时不讨论级数是否收敛。我们把它当作形式幂级数:x x x 只是一个占位符,两个生成函数相等,意思是每一项的系数都相等。
生成函数有用,是因为数列的递推关系会变成生成函数的代数关系。数列之间的运算,也会对应到生成函数之间的运算。
推斐波那契的生成函数
斐波那契数列满足
F 0 = 0 , F 1 = 1 , F n = F n − 1 + F n − 2 ( n ≥ 2 ) .
F_0=0,
\qquad
F_1=1,
\qquad
F_n=F_{n-1}+F_{n-2}\quad(n\ge2).
F 0 = 0 , F 1 = 1 , F n = F n − 1 + F n − 2 ( n ≥ 2 ) .
它的生成函数是
G ( x ) = F 0 + F 1 x + F 2 x 2 + F 3 x 3 + ⋯ .
G(x)=F_0+F_1x+F_2x^2+F_3x^3+\cdots.
G ( x ) = F 0 + F 1 x + F 2 x 2 + F 3 x 3 + ⋯ .
因为 F 0 = 0 , F 1 = 1 F_0=0,F_1=1 F 0 = 0 , F 1 = 1 ,所以
G ( x ) = x + x 2 + 2 x 3 + 3 x 4 + 5 x 5 + ⋯ .
G(x)=x+x^2+2x^3+3x^4+5x^5+\cdots.
G ( x ) = x + x 2 + 2 x 3 + 3 x 4 + 5 x 5 + ⋯ .
现在计算 G ( x ) − x G(x)-x G ( x ) − x :
G ( x ) − x = F 2 x 2 + F 3 x 3 + F 4 x 4 + ⋯ = ∑ n ≥ 2 F n x n = ∑ n ≥ 2 ( F n − 1 + F n − 2 ) x n = ∑ n ≥ 2 F n − 1 x n + ∑ n ≥ 2 F n − 2 x n = x ∑ m ≥ 1 F m x m + x 2 ∑ k ≥ 0 F k x k = x G ( x ) + x 2 G ( x ) .
\begin{aligned}
G(x)-x
&=F_2x^2+F_3x^3+F_4x^4+\cdots\\
&=\sum_{n\ge2}F_nx^n\\
&=\sum_{n\ge2}(F_{n-1}+F_{n-2})x^n\\
&=\sum_{n\ge2}F_{n-1}x^n+
\sum_{n\ge2}F_{n-2}x^n\\
&=x\sum_{m\ge1}F_mx^m+x^2\sum_{k\ge0}F_kx^k\\
&=xG(x)+x^2G(x).
\end{aligned}
G ( x ) − x = F 2 x 2 + F 3 x 3 + F 4 x 4 + ⋯ = n ≥ 2 ∑ F n x n = n ≥ 2 ∑ ( F n − 1 + F n − 2 ) x n = n ≥ 2 ∑ F n − 1 x n + n ≥ 2 ∑ F n − 2 x n = x m ≥ 1 ∑ F m x m + x 2 k ≥ 0 ∑ F k x k = x G ( x ) + x 2 G ( x ) .
因此
G ( x ) − x = x G ( x ) + x 2 G ( x ) .
G(x)-x=xG(x)+x^2G(x).
G ( x ) − x = x G ( x ) + x 2 G ( x ) .
整理得到
G ( x ) ( 1 − x − x 2 ) = x .
G(x)(1-x-x^2)=x.
G ( x ) ( 1 − x − x 2 ) = x .
所以
G ( x ) = x 1 − x − x 2 .
G(x)=\frac{x}{1-x-x^2}.
G ( x ) = 1 − x − x 2 x .
一个无穷数列,被压缩成了一个有理分式。
用生成函数推出闭式公式
生成函数还能直接推出 Binet 公式。
先把分母因式分解:
1 − x − x 2 = ( 1 − φ x ) ( 1 − ψ x ) ,
1-x-x^2=(1-\varphi x)(1-\psi x),
1 − x − x 2 = ( 1 − φ x ) ( 1 − ψ x ) ,
其中
φ = 1 + 5 2 , ψ = 1 − 5 2 .
\varphi=\frac{1+\sqrt5}{2},
\qquad
\psi=\frac{1-\sqrt5}{2}.
φ = 2 1 + 5 , ψ = 2 1 − 5 .
于是
G ( x ) = x ( 1 − φ x ) ( 1 − ψ x ) .
G(x)=\frac{x}{(1-\varphi x)(1-\psi x)}.
G ( x ) = ( 1 − φ x ) ( 1 − ψ x ) x .
做部分分式分解:
G ( x ) = 1 5 ( 1 1 − φ x − 1 1 − ψ x ) .
G(x)=
\frac1{\sqrt5}
\left(
\frac1{1-\varphi x}
-
\frac1{1-\psi x}
\right).
G ( x ) = 5 1 ( 1 − φ x 1 − 1 − ψ x 1 ) .
\frac1{1-\psi x}
\right).
验证也很直接:右边通分后,分子是
验证也很直接:右边通分后,分子是
验证也很直接:右边通分后,分子是
验证也很直接:右边通分后,分子是
( 1 − ψ x ) − ( 1 − φ x ) = ( φ − ψ ) x = 5 x .
(1-\psi x)-(1-\varphi x)=(\varphi-\psi)x=\sqrt5x.
( 1 − ψ x ) − ( 1 − φ x ) = ( φ − ψ ) x = 5 x .
所以右边确实等于
x ( 1 − φ x ) ( 1 − ψ x ) .
\frac{x}{(1-\varphi x)(1-\psi x)}.
( 1 − φ x ) ( 1 − ψ x ) x .
再用几何级数展开:
1 1 − a x = ∑ n ≥ 0 a n x n .
\frac1{1-ax}=\sum_{n\ge0}a^nx^n.
1 − a x 1 = n ≥ 0 ∑ a n x n .
于是
G ( x ) = 1 5 ( ∑ n ≥ 0 φ n x n − ∑ n ≥ 0 ψ n x n ) = ∑ n ≥ 0 φ n − ψ n 5 x n .
\begin{aligned}
G(x)
&=\frac1{\sqrt5}
\left(
\sum_{n\ge0}\varphi^nx^n-
\sum_{n\ge0}\psi^nx^n
\right)\\
&=\sum_{n\ge0}
\frac{\varphi^n-\psi^n}{\sqrt5}x^n.
\end{aligned}
G ( x ) = 5 1 ( n ≥ 0 ∑ φ n x n − n ≥ 0 ∑ ψ n x n ) = n ≥ 0 ∑ 5 φ n − ψ n x n .
另一方面,
G ( x ) = ∑ n ≥ 0 F n x n .
G(x)=\sum_{n\ge0}F_nx^n.
G ( x ) = n ≥ 0 ∑ F n x n .
对比系数,得到
F n = φ n − ψ n 5 .
F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.
F n = 5 φ n − ψ n .
这正是 Binet 公式。
这个过程说明:闭式公式、特征方程、生成函数分母因式分解,是同一件事的三种说法。
用生成函数推加和公式
生成函数还可以处理部分和。比如要证
F 1 + F 2 + ⋯ + F n = F n + 2 − 1.
F_1+F_2+\cdots+F_n=F_{n+2}-1.
F 1 + F 2 + ⋯ + F n = F n + 2 − 1.
部分和对应生成函数除以 1 − x 1-x 1 − x 。因为
1 1 − x = 1 + x + x 2 + x 3 + ⋯ ,
\frac1{1-x}=1+x+x^2+x^3+\cdots,
1 − x 1 = 1 + x + x 2 + x 3 + ⋯ ,
所以
G ( x ) 1 − x = ( ∑ n ≥ 0 F n x n ) ( ∑ n ≥ 0 x n ) .
\frac{G(x)}{1-x}
=
\left(\sum_{n\ge0}F_nx^n\right)
\left(\sum_{n\ge0}x^n\right).
1 − x G ( x ) = ( n ≥ 0 ∑ F n x n ) ( n ≥ 0 ∑ x n ) .
\left(\sum_{n\ge0}F_nx^n\right)
\left(\sum_{n\ge0}x^n\right).
乘开后,x n x^n x n 的系数是
F 0 + F 1 + ⋯ + F n .
F_0+F_1+\cdots+F_n.
F 0 + F 1 + ⋯ + F n .
这就是说,G ( x ) / ( 1 − x ) G(x)/(1-x) G ( x ) / ( 1 − x ) 编码了斐波那契数列的部分和。
代入 G ( x ) G(x) G ( x ) :
G ( x ) 1 − x = x ( 1 − x − x 2 ) ( 1 − x ) .
\frac{G(x)}{1-x}
=
\frac{x}{(1-x-x^2)(1-x)}.
1 − x G ( x ) = ( 1 − x − x 2 ) ( 1 − x ) x .
\frac{x}{(1-x-x^2)(1-x)}.
这条式子可以继续部分分式分解,得到部分和公式。即使不把分解细节写完,也可以看到一个重要规则:
这条式子可以继续部分分式分解,得到部分和公式。即使不把分解细节写完,也可以看到一个重要规则:
这条式子可以继续部分分式分解,得到部分和公式。即使不把分解细节写完,也可以看到一个重要规则:
这条式子可以继续部分分式分解,得到部分和公式。即使不把分解细节写完,也可以看到一个重要规则:
部分和 ⟺ 除以 1 − x .
\text{部分和}\quad\Longleftrightarrow\quad\text{除以 }1-x.
部分和 ⟺ 除以 1 − x .
生成函数把求和操作变成了代数操作。
生成函数作为压缩包
生成函数的本质,是把整个数列压成一个对象。
斐波那契数列有无穷多项:
F 0 , F 1 , F 2 , …
F_0,F_1,F_2,\ldots
F 0 , F 1 , F 2 , …
生成函数把它压成
G ( x ) = x 1 − x − x 2 .
G(x)=\frac{x}{1-x-x^2}.
G ( x ) = 1 − x − x 2 x .
这个分式里包含了整个数列的信息。
为什么这有用?因为对一个分式做代数运算,常常比对无穷多个系数逐项处理容易。
更一般地:
数列加法对应生成函数加法;
数列移位对应生成函数乘 x x x ;
数列卷积对应生成函数乘法;
数列部分和对应生成函数除以 1 − x 1-x 1 − x ;
数列差分对应生成函数乘以 1 − x 1-x 1 − x 。
这些对应让「证明数列恒等式」变成「做生成函数代数」。
生成函数和线性递推
斐波那契数列的生成函数分母是
它对应递推
F n = F n − 1 + F n − 2 .
F_n=F_{n-1}+F_{n-2}.
F n = F n − 1 + F n − 2 .
如果换成递推
a n = 3 a n − 1 − 2 a n − 2 ,
a_n=3a_{n-1}-2a_{n-2},
a n = 3 a n − 1 − 2 a n − 2 ,
生成函数的分母就会出现
1 − 3 x + 2 x 2 .
1-3x+2x^2.
1 − 3 x + 2 x 2 .
这不是巧合。对线性递推来说,生成函数通常是有理函数。分母记录递推关系,分子记录初值修正。
可以粗略记成:
线性递推 ⟺ 有理生成函数 .
\text{线性递推}\quad\Longleftrightarrow\quad\text{有理生成函数}.
线性递推 ⟺ 有理生成函数 .
这是生成函数处理线性递推的核心原因。
生成函数还能做什么
生成函数不只用来推闭式公式。它在组合数学里有很广的用途。
第一,处理带参数的计数。比如用 1 1 1 格砖和 2 2 2 格砖铺走廊,1 1 1 格砖有 a a a 种颜色,2 2 2 格砖有 b b b 种颜色,铺法生成函数会变成
1 1 − a x − b x 2 .
\frac1{1-ax-bx^2}.
1 − a x − b x 2 1 .
第二,处理多个数列之间的关系。斐波那契数列和 Lucas 数列有相同的递推,因此它们的生成函数分母相同,只是分子不同:
G F ( x ) = x 1 − x − x 2 , G L ( x ) = 2 − x 1 − x − x 2 .
G_F(x)=\frac{x}{1-x-x^2},
\qquad
G_L(x)=\frac{2-x}{1-x-x^2}.
G F ( x ) = 1 − x − x 2 x , G L ( x ) = 1 − x − x 2 2 − x .
第三,处理卷积递推。比如 Catalan 数满足
C ( x ) = 1 + x C ( x ) 2 .
C(x)=1+xC(x)^2.
C ( x ) = 1 + x C ( x ) 2 .
这个方程可以解出
C ( x ) = 1 − 1 − 4 x 2 x .
C(x)=\frac{1-\sqrt{1-4x}}{2x}.
C ( x ) = 2 x 1 − 1 − 4 x .
斐波那契只是生成函数的一个简单样本。学会这个样本后,许多计数问题都可以用类似方法处理。
生成函数不是万能的
最后看一个反例:Cassini 恒等式
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
F_{n+1}F_{n-1}-F_n^2=(-1)^n.
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
它涉及乘积 F n + 1 F n − 1 F_{n+1}F_{n-1} F n + 1 F n − 1 和平方 F n 2 F_n^2 F n 2 。生成函数擅长处理加法、移位、卷积,但不太擅长直接处理这种逐项乘积。
Cassini 用矩阵行列式最干净:
det ( M n ) = det ( M ) n .
\det(M^n)=\det(M)^n.
det ( M n ) = det ( M ) n .
这一步直接给出
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
F_{n+1}F_{n-1}-F_n^2=(-1)^n.
F n + 1 F n − 1 − F n 2 = ( − 1 ) n .
这个例子说明:每条恒等式都有自己的自然语言。加和公式适合生成函数或组合计数,Cassini 适合矩阵行列式,翻倍公式适合矩阵平方或加法公式。
本章小结
生成函数把整个数列压成一个对象。斐波那契数列的生成函数是
G ( x ) = x 1 − x − x 2 .
G(x)=\frac{x}{1-x-x^2}.
G ( x ) = 1 − x − x 2 x .
它让闭式公式的推导变成机械过程:递推变成生成函数,分母因式分解,部分分式展开,再对比系数。
生成函数也把数列运算变成代数运算。部分和对应除以 1 − x 1-x 1 − x ,移位对应乘 x x x ,卷积对应乘法。
但生成函数不是万能的。它对线性结构很强,对 Cassini 这类逐项乘积不直接。不同恒等式有不同的自然语言。下一章开始,我们把 F n F_n F n 当作整数看,研究它的整除性和模周期。
第 16 章 整除性:下标带算术信息
到这一章为止,我们把斐波那契数列从各种角度看过了一遍:递归、算法、矩阵、闭式、组合、图论、恒等式、生成函数。每一种视角都把同一组数翻译成一种语言。
这一章换一个新视角:把 F n F_n F n 当作整数看,问它有什么"整数性质"。
具体来说:F n F_n F n 之间能整除吗?什么时候 F m F_{m} F m 能整除 F n F_n F n ?两个斐波那契数的最大公约数是什么?
这些问题看起来像数论趣闻。但是它们其实揭示了一件事:F n F_n F n 的下标 n 不只是一个标号,它本身带着算术信息。n 的整除性质会反映到 F n F_n F n 的整除性质上。
先列出来观察
把前若干个 F n F_n F n 列出来:
n n n
F n F_n F n
n n n
F n F_n F n
n n n
F n F_n F n
1
1
11
89
21
10946
2
1
12
144
22
17711
3
2
13
233
23
28657
4
3
14
377
24
46368
5
5
15
610
25
75025
6
8
16
987
26
121393
7
13
17
1597
27
196418
8
21
18
2584
28
317811
9
34
19
4181
29
514229
10
55
20
6765
30
832040
哪些是偶数?F 3 = 2 , F 6 = 8 , F 9 = 34 , F 12 = 144 , F 15 = 610 , F 18 = 2584 , F 21 = 10946 , F 24 = 46368 , F 27 = 196418 , F 30 = 832040 F_{3} = 2, F_{6} = 8, F_{9} = 34, F_{12} = 144, F_{15} = 610, F_{18} = 2584, F_{21} = 10946, F_{24} = 46368, F_{27} = 196418, F_{30} = 832040 F 3 = 2 , F 6 = 8 , F 9 = 34 , F 12 = 144 , F 15 = 610 , F 18 = 2584 , F 21 = 10946 , F 24 = 46368 , F 27 = 196418 , F 30 = 832040 。
下标:3, 6, 9, 12, 15, 18, 21, 24, 27, 30。每 3 项出现一个偶数。F n F_n F n 是偶数当且仅当 n 是 3 的倍数。
哪些能被 3 整除?F 4 = 3 , F 8 = 21 , F 12 = 144 , F 16 = 987 , F 20 = 6765 , F 24 = 46368 , F 28 = 317811 F_{4} = 3, F_{8} = 21, F_{12} = 144, F_{16} = 987, F_{20} = 6765, F_{24} = 46368, F_{28} = 317811 F 4 = 3 , F 8 = 21 , F 12 = 144 , F 16 = 987 , F 20 = 6765 , F 24 = 46368 , F 28 = 317811 。
下标:4, 8, 12, 16, 20, 24, 28。每 4 项。F n F_n F n 被 3 整除当且仅当 n 是 4 的倍数。
哪些能被 5 整除?F 5 = 5 , F 10 = 55 , F 15 = 610 , F 20 = 6765 , F 25 = 75025 , F 30 = 832040 F_{5} = 5, F_{10} = 55, F_{15} = 610, F_{20} = 6765, F_{25} = 75025, F_{30} = 832040 F 5 = 5 , F 10 = 55 , F 15 = 610 , F 20 = 6765 , F 25 = 75025 , F 30 = 832040 。
下标:5, 10, 15, 20, 25, 30。每 5 项。F n F_n F n 被 5 整除当且仅当 n 是 5 的倍数。
规律浮现:F n F_n F n 被 F k F_{k} F k 整除似乎和 n 被 k 整除有关。具体来说:
当 m ≥ 3 m \ge 3 m ≥ 3 时,F m F_m F m 整除 F n F_n F n 当且仅当 m m m 整除 n n n 。若 m = 1 , 2 m = 1, 2 m = 1 , 2 ,因 F m = 1 F_m = 1 F m = 1 ,总是整除。
这是一个非常干净的结论。它说斐波那契数列的"整除结构"和"下标的整除结构"是同构的。
推一下:F m F_{m} F m 整除 F k m F_{km} F km
我们先证明一个弱一点的版本:如果 m 整除 n 且 m ≥ \geq ≥ 3,那么 F m F_{m} F m 整除 F n F_n F n 。
n 是 m 的倍数,写成 n = km。我们要证 F m F_{m} F m | F k m F_{km} F km 。
回忆第 5 章的加法公式:
F n + m = F n + 1 F m + F n F m − 1 F_{n+m} = F_{n+1} F_{m} + F_{n} F_{m-1} F n + m = F n + 1 F m + F n F m − 1
变形一下,让 n = ( k − 1 ) m n = (k-1)m n = ( k − 1 ) m :
F k m = F ( k − 1 ) m + m = F ( k − 1 ) m + 1 ⋅ F m + F ( k − 1 ) m ⋅ F m − 1 .
F_{km} = F_{(k-1)m + m} = F_{(k-1)m + 1} \cdot F_m + F_{(k-1)m} \cdot F_{m-1}.
F km = F ( k − 1 ) m + m = F ( k − 1 ) m + 1 ⋅ F m + F ( k − 1 ) m ⋅ F m − 1 .
所以 F k m = F m ⋅ F ( k − 1 ) m + 1 + F ( k − 1 ) m ⋅ F m − 1 F_{km} = F_m \cdot F_{(k-1)m + 1} + F_{(k-1)m} \cdot F_{m-1} F km = F m ⋅ F ( k − 1 ) m + 1 + F ( k − 1 ) m ⋅ F m − 1 。
如果 F m F_m F m 整除 F ( k − 1 ) m F_{(k-1)m} F ( k − 1 ) m ,那么 F m F_m F m 整除上面这个等式的右边(两项都被 F m F_m F m 整除),所以 F m F_m F m 整除 F k m F_{km} F km 。
这是对 k k k 的归纳。基础 k = 1 k = 1 k = 1 :F m F_m F m 整除 F m F_m F m ,显然。归纳步从 k − 1 k-1 k − 1 推到 k k k ,刚才那段就是。
所以 F m ∣ F k m F_m \mid F_{km} F m ∣ F km ,即 m ∣ n m \mid n m ∣ n 蕴含 F m ∣ F n F_m \mid F_n F m ∣ F n 。
反过来呢?如果 F m ∣ F n F_m \mid F_n F m ∣ F n ,是不是 m ∣ n m \mid n m ∣ n ?
也是。这个方向的证明稍微绕一点,需要用到一个更强的结论,下一节的 gcd 公式。
最大公约数定理
最强的整除性结论是:
gcd ( F m , F n ) = F gcd ( m , n ) .
\gcd(F_m, F_n) = F_{\gcd(m, n)}.
g cd( F m , F n ) = F g c d ( m , n ) .
也就是说,两个斐波那契数的最大公约数,等于它们下标最大公约数对应的斐波那契数。
这个定理一上来可能让人愣一下。它说斐波那契数列的"算术结构"完全镜像了下标的算术结构。下标的 gcd 决定斐波那契数的 gcd。
举几个例子验证:
gcd ( F 6 , F 9 ) = gcd ( 8 , 34 ) = 2 = F 3 = F gcd ( 6 , 9 ) \gcd(F_6, F_9) = \gcd(8, 34) = 2 = F_3 = F_{\gcd(6, 9)} g cd( F 6 , F 9 ) = g cd( 8 , 34 ) = 2 = F 3 = F g c d ( 6 , 9 ) 。对。
gcd ( F 8 , F 12 ) = gcd ( 21 , 144 ) = 3 = F 4 = F gcd ( 8 , 12 ) \gcd(F_8, F_{12}) = \gcd(21, 144) = 3 = F_4 = F_{\gcd(8, 12)} g cd( F 8 , F 12 ) = g cd( 21 , 144 ) = 3 = F 4 = F g c d ( 8 , 12 ) 。对。
gcd ( F 10 , F 15 ) = gcd ( 55 , 610 ) = 5 = F 5 = F gcd ( 10 , 15 ) \gcd(F_{10}, F_{15}) = \gcd(55, 610) = 5 = F_5 = F_{\gcd(10, 15)} g cd( F 10 , F 15 ) = g cd( 55 , 610 ) = 5 = F 5 = F g c d ( 10 , 15 ) 。对。
gcd ( F 12 , F 18 ) = gcd ( 144 , 2584 ) = 2 = F 6 = F gcd ( 12 , 18 ) \gcd(F_{12}, F_{18}) = \gcd(144, 2584) = 2 = F_6 = F_{\gcd(12, 18)} g cd( F 12 , F 18 ) = g cd( 144 , 2584 ) = 2 = F 6 = F g c d ( 12 , 18 ) 。对。
公式稳。
推一下 gcd 公式
证明 gcd ( F m , F n ) = F gcd ( m , n ) \gcd(F_m, F_n) = F_{\gcd(m, n)} g cd( F m , F n ) = F g c d ( m , n ) 需要用到一个关键引理:
gcd ( F m , F m + n ) = gcd ( F m , F n ) .
\gcd(F_m, F_{m+n}) = \gcd(F_m, F_n).
g cd( F m , F m + n ) = g cd( F m , F n ) .
为什么呢?加法公式给出 F m + n = F m + 1 F n + F m F n − 1 F_{m+n} = F_{m+1} F_n + F_m F_{n-1} F m + n = F m + 1 F n + F m F n − 1 。所以:
gcd ( F m , F m + n ) = gcd ( F m , F m + 1 F n + F m F n − 1 ) .
\gcd(F_m, F_{m+n}) = \gcd(F_m, F_{m+1} F_n + F_m F_{n-1}).
g cd( F m , F m + n ) = g cd( F m , F m + 1 F n + F m F n − 1 ) .
任何 F m F_m F m 和 F m + 1 F_{m+1} F m + 1 的公因子都同时整除 F m F_m F m 和 F m + 1 F_{m+1} F m + 1 。但 F m F_m F m 和 F m + 1 F_{m+1} F m + 1 互素(连续两项互素,因为 F m + 1 = F m + F m − 1 F_{m+1} = F_m + F_{m-1} F m + 1 = F m + F m − 1 ,所以 gcd ( F m , F m + 1 ) = gcd ( F m , F m − 1 ) = ⋯ = gcd ( F 1 , F 2 ) = 1 \gcd(F_m, F_{m+1}) = \gcd(F_m, F_{m-1}) = \cdots = \gcd(F_1, F_2) = 1 g cd( F m , F m + 1 ) = g cd( F m , F m − 1 ) = ⋯ = g cd( F 1 , F 2 ) = 1 )。
那么 gcd ( F m , F m + 1 F n + F m F n − 1 ) = gcd ( F m , F m + 1 F n ) \gcd(F_m, F_{m+1} F_n + F_m F_{n-1}) = \gcd(F_m, F_{m+1} F_n) g cd( F m , F m + 1 F n + F m F n − 1 ) = g cd( F m , F m + 1 F n ) 。因为 F m F_m F m 整除 F m F n − 1 F_m F_{n-1} F m F n − 1 那一项,可以扔掉。
而 gcd ( F m , F m + 1 F n ) = gcd ( F m , F n ) \gcd(F_m, F_{m+1} F_n) = \gcd(F_m, F_n) g cd( F m , F m + 1 F n ) = g cd( F m , F n ) ,因为 F m F_m F m 和 F m + 1 F_{m+1} F m + 1 互素。
所以 gcd ( F m , F m + n ) = gcd ( F m , F n ) \gcd(F_m, F_{m+n}) = \gcd(F_m, F_n) g cd( F m , F m + n ) = g cd( F m , F n ) 。
这个等式是关键。它说"两个斐波那契数的 gcd"满足"辗转相减"的规则:把较大的下标换成差,gcd 不变。
回忆欧几里得算法:gcd ( m , n ) = gcd ( m , n − m ) \gcd(m, n) = \gcd(m, n-m) g cd( m , n ) = g cd( m , n − m ) (当 n > m n > m n > m 时)。我们的引理说 gcd ( F m , F n ) = gcd ( F m , F n − m ) \gcd(F_m, F_n) = \gcd(F_m, F_{n-m}) g cd( F m , F n ) = g cd( F m , F n − m ) 。这正好是欧几里得算法在"斐波那契下标"上的镜像。
反复应用这个等式,相当于在 ( m , n ) (m, n) ( m , n ) 上跑欧几里得算法:
gcd ( F m , F n ) = gcd ( F m , F n − m ) = gcd ( F m , F n − 2 m ) = ⋯ = gcd ( F m , F n m o d m ) .
\gcd(F_m, F_n) = \gcd(F_m, F_{n-m}) = \gcd(F_m, F_{n-2m}) = \cdots = \gcd(F_m, F_{n \bmod m}).
g cd( F m , F n ) = g cd( F m , F n − m ) = g cd( F m , F n − 2 m ) = ⋯ = g cd( F m , F n mod m ) .
然后倒过来继续:
gcd ( F m , F n m o d m ) = gcd ( F m m o d ( n m o d m ) , F n m o d m ) = ⋯
\gcd(F_m, F_{n \bmod m}) = \gcd(F_{m \bmod (n \bmod m)}, F_{n \bmod m}) = \cdots
g cd( F m , F n mod m ) = g cd( F m mod ( n mod m ) , F n mod m ) = ⋯
一直跑到两边相等,得到 gcd ( F g , F g ) = F g \gcd(F_g, F_g) = F_g g cd( F g , F g ) = F g ,其中 g = gcd ( m , n ) g = \gcd(m, n) g = g cd( m , n ) 。
所以 gcd ( F m , F n ) = F gcd ( m , n ) \gcd(F_m, F_n) = F_{\gcd(m, n)} g cd( F m , F n ) = F g c d ( m , n ) 。
这个定理说明了什么
gcd ( F m , F n ) = F gcd ( m , n ) \gcd(F_m, F_n) = F_{\gcd(m, n)} g cd( F m , F n ) = F g c d ( m , n ) 这个定理,在数论里非常特别。
它说斐波那契数列的"算术结构"(哪些项能整除哪些项、两个项的最大公约数是多少)完全由"下标的算术结构"决定。
也就是说,下标 n n n 不只是一个标号。它带着算术信息。n n n 的因子结构、n n n 和其他下标的 gcd,这些信息直接反映到 F n F_n F n 的因子结构上。
这种"下标 ↔ 值"的算术对应,是非常特殊的。大多数数列没有这种性质。比如:
平方数列 n 2 n^2 n 2 :实际上满足 m ∣ n ⇒ m 2 ∣ n 2 m \mid n \Rightarrow m^2 \mid n^2 m ∣ n ⇒ m 2 ∣ n 2 ,所以它也有"下标整除蕴含值整除"的性质,但它没有斐波那契的 gcd 镜像结构,即 gcd ( m 2 , n 2 ) = ( gcd ( m , n ) ) 2 \gcd(m^2, n^2) = (\gcd(m,n))^2 g cd( m 2 , n 2 ) = ( g cd( m , n ) ) 2 虽然成立,但镜像不如斐波那契那么干净。
阶乘数列 n ! n! n ! :m ∣ n m \mid n m ∣ n 不蕴含 m ! ∣ n ! m! \mid n! m ! ∣ n ! (实际上 m ! m! m ! 整除 n ! n! n ! 当 m ≤ n m \le n m ≤ n ,这和下标整除无关)。
素数数列 p n p_n p n :m ∣ n m \mid n m ∣ n 不蕴含 p m ∣ p n p_m \mid p_n p m ∣ p n (5 ∣ 10 5 \mid 10 5 ∣ 10 但 p 5 = 11 p_5 = 11 p 5 = 11 不整除 p 10 = 29 p_{10} = 29 p 10 = 29 )。
所以斐波那契数列的"下标-值算术对应"是一种特殊的、不是所有数列都有的性质。这种性质来自斐波那契数列的"线性递推 + 整数系数"结构。矩阵 M M M 的整数性质让 F m F_m F m 和 F n F_n F n 之间的算术关系镜像 m m m 和 n n n 之间的关系。
一个有趣的副产品:F p F_p F p 和素数 p p p
把整除定理用一下,可以推出一些关于素数 p p p 的有趣性质。
如果 p p p 是素数,那么 gcd ( F p , F n ) \gcd(F_p, F_n) g cd( F p , F n ) 只能是 1 1 1 或 F p F_p F p 。具体来说,gcd ( F p , F n ) = F gcd ( p , n ) = F 1 = 1 \gcd(F_p, F_n) = F_{\gcd(p, n)} = F_1 = 1 g cd( F p , F n ) = F g c d ( p , n ) = F 1 = 1 (如果 p p p 不整除 n n n )或 F p F_p F p (如果 p p p 整除 n n n )。
所以 F p F_p F p 和"下标不被 p p p 整除的斐波那契数"都互素。这有点像"p p p 是素数 ⟺ \iff ⟺ p p p 和所有比它小的正整数互素"在斐波那契数列里的镜像。
更深的数论性质(比如 Pisano 周期和 F p m o d p F_p \bmod p F p mod p 的关系)会出现在下一章。这里只是让你看到,整除定理不光是个孤立结论,它和数论里更深的结构连着。
整除性给我们的视角
到这里可以收一下。
整除性定理说:当 m ≥ 3 m \ge 3 m ≥ 3 时,F m ∣ F n F_m \mid F_n F m ∣ F n 当且仅当 m ∣ n m \mid n m ∣ n 。gcd ( F m , F n ) = F gcd ( m , n ) \gcd(F_m, F_n) = F_{\gcd(m, n)} g cd( F m , F n ) = F g c d ( m , n ) 。
这两件事给我们的视角是:斐波那契数列的"算术结构"和"下标的算术结构"同构。下标不只是一个标号,它带着算术信息。
这种视角在数学里非常珍贵。它意味着如果你想研究 F n F_n F n 的因子结构,你可以转而研究 n n n 的因子结构,后者通常更容易。
举一个具体的例子:要知道 F 100 F_{100} F 100 能被哪些斐波那契数整除,我们不需要去算 F 100 = 354224848179261915075 F_{100} = 354224848179261915075 F 100 = 354224848179261915075 然后做因子分解。我们只需要找出 100 100 100 的所有因子:1 , 2 , 4 , 5 , 10 , 20 , 25 , 50 , 100 1, 2, 4, 5, 10, 20, 25, 50, 100 1 , 2 , 4 , 5 , 10 , 20 , 25 , 50 , 100 。所以 F 100 F_{100} F 100 被 F 1 = 1 F_1 = 1 F 1 = 1 ,F 2 = 1 F_2 = 1 F 2 = 1 ,F 4 = 3 F_4 = 3 F 4 = 3 ,F 5 = 5 F_5 = 5 F 5 = 5 ,F 10 = 55 F_{10} = 55 F 10 = 55 ,F 20 = 6765 F_{20} = 6765 F 20 = 6765 ,F 25 = 75025 F_{25} = 75025 F 25 = 75025 ,F 50 F_{50} F 50 ,F 100 F_{100} F 100 这些斐波那契数整除。
不需要算大数。下标的因子结构就够了。
这就是"下标带算术信息"这件事的力量。
数论作为"算术结构的研究"
最后说一下数论在做什么。
数论经常被描述为"研究整数的性质"。这个描述没错,但太宽。更精确一点说,数论研究的是"算术结构",加法、乘法、整除、模运算这些操作下,整数系统里有哪些规律。
斐波那契数列给数论提供了一个具体的舞台。它是一个由整数组成的数列,但是它的算术结构(哪些项整除哪些项、gcd 是什么)和它的下标的算术结构有紧密联系。这种联系不是显然的,需要证明。但是一旦证出来,就给出一种"通过下标算 F n F_n F n "的捷径。
下一章我们继续这种"算术结构"的探索,但视角更窄,只看余数。看 F n m o d m F_n \bmod m F n mod m 的余数有什么规律。结果会发现一个让人意想不到的事实:F n m o d m F_n \bmod m F n mod m 总会循环。这种循环是有限状态系统的必然结果。
第 17 章 模运算与 Pisano 周期:发现循环
这一章只看斐波那契数列的最后一位数字。
听起来好像很窄。但是这一位数字里藏着一个非常漂亮的事实:它每 60 项循环一次。
我们会把这个循环解释清楚。结论是:任何有限状态系统都会循环,斐波那契数列模 m 的余数序列是这种循环的最干净的样本。
先把循环列出来
把 F n F_n F n mod 10 列出来(也就是 F n F_n F n 的最后一位):
n: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
F_n: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610
mod 10: 0 1 1 2 3 5 8 3 1 4 5 9 4 3 7 0
继续:
n: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
F_n: 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040
mod 10: 7 7 4 1 5 6 1 7 8 5 3 8 1 9 0
继续:
n: 31 32 33 34 35 36 37 38 39 40
F_n: 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155
mod 10: 9 9 8 7 5 2 7 9 6 5
继续:
n: 41 42 43 44 45 46 47 48 49 50
mod 10: 1 6 7 3 0 3 3 6 9 5
继续:
n: 51 52 53 54 55 56 57 58 59 60
mod 10: 4 9 3 2 5 7 2 9 1 0
到 n = 60,mod 10 = 0。然后 n = 61 应该是 1(F 61 F_{61} F 61 mod 10),n = 62 n = 62 n = 62 应该是 1。事实上:
n: 60 61 62 63 64 ...
mod 10: 0 1 1 2 3 ...
从 n = 60 开始,序列回到 0, 1, 1, 2, 3, …,和从 n = 0 开始完全一样。
也就是说,F n F_n F n mod 10 的序列,每 60 项循环一次。
这个 60 叫"模 10 的 Pisano 周期",记 π ( 10 ) = 60 \pi(10)=60 π ( 10 ) = 60 。
先看几个小模数
60 这个数字看起来很神秘。先看几个小模数,找找规律。
模 2 :
n: 0 1 2 3 4 5 6 7 8 9 10 ...
mod 2: 0 1 1 0 1 1 0 1 1 0 1 ...
每 3 项循环。π ( 2 ) = 3 \pi(2) = 3 π ( 2 ) = 3 。
模 3 :
n: 0 1 2 3 4 5 6 7 8 9 10 11 12 ...
mod 3: 0 1 1 2 0 2 2 1 0 1 1 2 0 ...
每 8 项循环。π ( 3 ) = 8 \pi(3) = 8 π ( 3 ) = 8 。
模 4 :
n: 0 1 2 3 4 5 6 7 8 9 10 11 12 ...
mod 4: 0 1 1 2 3 1 0 1 1 2 3 1 0 ...
每 6 项循环。π ( 4 ) = 6 \pi(4) = 6 π ( 4 ) = 6 。
模 5 :
n: 0 1 2 3 4 5 6 7 8 9 10 ...
mod 5: 0 1 1 2 3 0 3 3 1 4 0 ...
每 20 项循环。π ( 5 ) = 20 \pi(5) = 20 π ( 5 ) = 20 。
把前若干个 Pisano 周期列出来:
m: 1 2 3 4 5 6 7 8 9 10 11 12
π(m): 1 3 8 6 20 24 16 12 24 60 10 24
观察:
π ( 2 ) = 3 π ( 4 ) = 6 = 2 ⋅ π ( 2 ) π ( 8 ) = 12 = 2 ⋅ π ( 4 ) π ( 16 ) = 24 = 2 ⋅ π ( 8 )
\pi(2) = 3
\pi(4) = 6 = 2 \cdot \pi(2)
\pi(8) = 12 = 2 \cdot \pi(4)
\pi(16) = 24 = 2 \cdot \pi(8)
π ( 2 ) = 3 π ( 4 ) = 6 = 2 ⋅ π ( 2 ) π ( 8 ) = 12 = 2 ⋅ π ( 4 ) π ( 16 ) = 24 = 2 ⋅ π ( 8 )
似乎
π ( 2 k ) = 3 ⋅ 2 k − 1 .
\pi(2^k)=3\cdot2^{k-1}.
π ( 2 k ) = 3 ⋅ 2 k − 1 .
π ( 5 ) = 20 π ( 25 ) = 100 = 5 ⋅ π ( 5 )
\pi(5) = 20
\pi(25) = 100 = 5 \cdot \pi(5)
π ( 5 ) = 20 π ( 25 ) = 100 = 5 ⋅ π ( 5 )
似乎
π ( 5 k ) = 4 ⋅ 5 k .
\pi(5^k)=4\cdot5^k.
π ( 5 k ) = 4 ⋅ 5 k .
π ( 10 ) = 60 = lcm ( π ( 2 ) , π ( 5 ) ) = lcm ( 3 , 20 ) = 60 \pi(10) = 60 = \operatorname{lcm}(\pi(2), \pi(5)) = \operatorname{lcm}(3, 20) = 60 π ( 10 ) = 60 = lcm ( π ( 2 ) , π ( 5 )) = lcm ( 3 , 20 ) = 60 。也就是说,模 10 的周期是模 2 周期和模 5 周期的最小公倍数。
这些观察里,最重要的是最后一条:π ( m 1 m 2 ) = lcm ( π ( m 1 ) , π ( m 2 ) ) \pi(m_1m_2)=\operatorname{lcm}(\pi(m_1),\pi(m_2)) π ( m 1 m 2 ) = lcm ( π ( m 1 ) , π ( m 2 )) ,当 m 1 m_1 m 1 和 m 2 m_2 m 2 互素时。这件事下一章会用,叫"中国剩余定理拆分"。
为什么必然循环
现在到关键问题:为什么 F n F_n F n mod m 总是循环?
答案是:因为它是有限状态系统。
考虑"状态对" (F n F_n F n mod m, F n + 1 F_{n+1} F n + 1 mod m)。这个状态对的取值范围是 {0, 1, …, m-1} × \times × {0, 1, …, m-1},总共 m 2 m^2 m 2 种可能。
按斐波那契递推,下一对状态是 (F n + 1 F_{n+1} F n + 1 mod m, F n + 2 F_{n+2} F n + 2 mod m) = (F n + 1 F_{n+1} F n + 1 mod m, (F n F_n F n + F n + 1 F_{n+1} F n + 1 ) mod m)。这个变换完全由当前状态决定,不需要其他信息。
所以这是一个"状态确定下一状态"的有限状态机:m 2 m^2 m 2 个状态,每个状态有一个唯一的"下一个状态"。
从初始状态 ( F 0 , F 1 ) = ( 0 , 1 ) (F_0,F_1)=(0,1) ( F 0 , F 1 ) = ( 0 , 1 ) 出发,按这个机器走 m 2 m^2 m 2 + 1 步,必然会重复某个状态。鸽巢原理:m 2 m^2 m 2 + 1 个状态里至少有两个相同。
一旦某个状态重复,从它出发的后续轨迹也必然相同,因为状态转移是确定的。所以从第一次重复开始,整个序列进入循环。
也就是说,F n F_n F n mod m 的序列,必然在 m 2 m^2 m 2 + 1 步内进入循环。循环长度 ≤ \leq ≤ m 2 m^2 m 2 。
这就是 Pisano 周期的本质:有限状态系统的必然周期性。
鸽巢原理的具体应用
把这件事再讲细一点。
鸽巢原理说:把 n+1 个物体放进 n 个抽屉,至少有一个抽屉里有至少两个物体。
这里"物体"是状态对,"抽屉"是 m 2 m^2 m 2 种可能的状态对。从 (0, 1) 出发,走 m 2 m^2 m 2 + 1 步产生 m 2 m^2 m 2 + 1 个状态对。它们都属于 m 2 m^2 m 2 种可能,所以至少有两个相同。
设这两个状态对分别出现在第 i 步和第 j 步(i < j)。那么 (F i F_{i} F i , F i + 1 F_{i+1} F i + 1 ) ≡ \equiv ≡ (F j F_{j} F j , F j + 1 F_{j+1} F j + 1 ) mod m。
从这两个相同状态出发,后续轨迹必然相同。所以对任何 k ≥ \geq ≥ 0,(F i + k F_{i+k} F i + k , F i + k + 1 F_{i+k+1} F i + k + 1 ) ≡ \equiv ≡ (F j + k F_{j+k} F j + k , F j + k + 1 F_{j+k+1} F j + k + 1 ) mod m。
也就是说,从 i 开始,序列以 j - i 为周期循环。
注意 j - i ≤ \leq ≤ m 2 m^2 m 2 。所以循环长度不超过 m 2 m^2 m 2 。这是 Pisano 周期的上界。
实际周期可能远小于 m 2 m^2 m 2 。比如 m = 10,m 2 = 100 m^2=100 m 2 = 100 ,但实际 π ( 10 ) = 60 \pi(10)=60 π ( 10 ) = 60 。m = 100 m = 100 m = 100 ,m 2 = 10000 m^2=10000 m 2 = 10000 ,实际 π ( 100 ) = 300 \pi(100)=300 π ( 100 ) = 300 。m = 1000 m = 1000 m = 1000 ,m 2 = 1 0 6 m^2=10^6 m 2 = 1 0 6 ,实际 π ( 1000 ) = 1500 \pi(1000)=1500 π ( 1000 ) = 1500 。
实际周期比上界小很多。具体周期是多少取决于 m 的代数结构,这一点下一章会展开。
这章的核心:必然循环这件事本身不需要深度数论
到这里可以下个判断。
F n F_n F n mod m 的余数序列必然循环,循环长度 π ( m ) \pi(m) π ( m ) ≤ \leq ≤ m 2 m^2 m 2 。这是有限状态系统的必然结果。
这个结论干净利落,不依赖任何深度数论。它的本质是:
F n F_n F n 的递推只需要前两项。
模 m 下,前两项的取值范围有限(m 2 m^2 m 2 种)。
有限状态 + 确定转移 = 必然循环。
这就是斐波那契数列个位数循环的真正原因。鸽巢原理就够了。
具体周期 π ( m ) \pi(m) π ( m ) 是多少,是一个深得多的问题,涉及模素数的特征根、二次剩余、二次互反律。但是"必然循环"这件事本身,只需要鸽巢原理就够了。
一个直观画面
最后留一个画面。
把"状态对"想象成棋盘上的格子。棋盘是 m × \times × m 的,每个格子代表一个 (F n F_n F n , F n + 1 F_{n+1} F n + 1 ) mod m 的状态对。从 (0, 1) 这个格子出发,按递推走,每次跳到一个新格子。
棋盘只有 m 2 m^2 m 2 个格子。走 m 2 m^2 m 2 + 1 步必然重复一个格子。重复之后,后面的轨迹就和第一次经过那个格子时一样。所以序列必然进入循环。
m = 10 m=10 m = 10 时,棋盘有 10 × 10 = 100 10 \times 10=100 10 × 10 = 100 个格子。F n F_n F n mod 10 的序列走过 60 个不同的格子后回到起点,形成 60 步的循环。剩下 40 个格子没被访问。
m = 2 m=2 m = 2 时,棋盘有 2 × 2 = 4 2\times2=4 2 × 2 = 4 个格子。F n F_n F n mod 2 的序列走过 3 个格子后回到起点,形成 3 步循环。1 个格子没被访问。
这种"棋盘上的轨迹"画面让 Pisano 周期变得可视。循环不是抽象的代数事实,是棋盘上轨迹回到起点的几何事实。
本章小结
F n F_n F n mod m 的余数序列必然循环。这个事实不依赖深度数论,鸽巢原理就够了。
具体周期 π ( m ) \pi(m) π ( m ) 因 m 而异:π ( 2 ) = 3 , π ( 3 ) = 8 , π ( 4 ) = 6 , π ( 5 ) = 20 , π ( 10 ) = 60 \pi(2) = 3, \pi(3) = 8, \pi(4) = 6, \pi(5) = 20, \pi(10) = 60 π ( 2 ) = 3 , π ( 3 ) = 8 , π ( 4 ) = 6 , π ( 5 ) = 20 , π ( 10 ) = 60 。周期上界是 m 2 m^2 m 2 。
模 10 的周期是 60。这个数字看起来神秘,但是下一章我们会看到,60 = lcm ( π ( 2 ) , π ( 5 ) ) = lcm ( 3 , 20 ) 60 = \operatorname{lcm}(\pi(2), \pi(5)) = \operatorname{lcm}(3, 20) 60 = lcm ( π ( 2 ) , π ( 5 )) = lcm ( 3 , 20 ) 。模 10 的周期是模 2 周期和模 5 周期的最小公倍数,由中国剩余定理保证。
鸽巢原理是这一章的全部精髓。它告诉我们:任何"状态确定下一状态"的有限系统都会循环。看见任何有限状态系统,第一反应应该是"它会循环"。
下一章我们解释为什么 π ( 10 ) = 60 \pi(10)=60 π ( 10 ) = 60 ,深入到素数情况和二次剩余。
第 18 章 Pisano 周期深入:素数与中国剩余定理
上一章我们发现,F n m o d m F_n\bmod m F n mod m 必然循环,而且 π ( 10 ) = 60 \pi(10)=60 π ( 10 ) = 60 。但 60 60 60 这个数字看上去有点突然。这一章解释它从哪儿来。
主要工具有两个:中国剩余定理,以及素数模数下的特征根分析。
中国剩余定理:把模拆开
中国剩余定理说:如果 m 1 m_1 m 1 和 m 2 m_2 m 2 互素,那么模 m 1 m 2 m_1m_2 m 1 m 2 的余数,等价于同时知道模 m 1 m_1 m 1 和模 m 2 m_2 m 2 的余数。也就是说,对任何整数 a a a ,下面这对值
( a m o d m 1 , a m o d m 2 )
(a\bmod m_1,\,a\bmod m_2)
( a mod m 1 , a mod m 2 )
唯一确定 a m o d m 1 m 2 a\bmod m_1m_2 a mod m 1 m 2 。
把这个定理用到斐波那契数列上,模 m 1 m 2 m_1m_2 m 1 m 2 的状态
( F n , F n + 1 ) m o d m 1 m 2
(F_n,F_{n+1})\bmod m_1m_2
( F n , F n + 1 ) mod m 1 m 2
等价于同时知道
( F n , F n + 1 ) m o d m 1
(F_n,F_{n+1})\bmod m_1
( F n , F n + 1 ) mod m 1
和
( F n , F n + 1 ) m o d m 2 .
(F_n,F_{n+1})\bmod m_2.
( F n , F n + 1 ) mod m 2 .
如果模 m 1 m_1 m 1 的周期是 π ( m 1 ) \pi(m_1) π ( m 1 ) ,模 m 2 m_2 m 2 的周期是 π ( m 2 ) \pi(m_2) π ( m 2 ) ,那么模 m 1 m 2 m_1m_2 m 1 m 2 的状态要等两边同时回到起点,才会回到起点。最小的共同回归时间是最小公倍数。
因此,当 gcd ( m 1 , m 2 ) = 1 \gcd(m_1,m_2)=1 g cd( m 1 , m 2 ) = 1 时,
π ( m 1 m 2 ) = lcm ( π ( m 1 ) , π ( m 2 ) ) .
\pi(m_1m_2)=\operatorname{lcm}\bigl(\pi(m_1),\pi(m_2)\bigr).
π ( m 1 m 2 ) = lcm ( π ( m 1 ) , π ( m 2 ) ) .
这条公式把「模合数的周期」拆成了「模素数幂的周期」的最小公倍数。
拆开 π ( 10 ) = 60 \pi(10)=60 π ( 10 ) = 60
因为
10 = 2 × 5 , gcd ( 2 , 5 ) = 1 ,
10=2\times5,
\qquad
\gcd(2,5)=1,
10 = 2 × 5 , g cd( 2 , 5 ) = 1 ,
所以
π ( 10 ) = lcm ( π ( 2 ) , π ( 5 ) ) = lcm ( 3 , 20 ) = 60.
\pi(10)
=
\operatorname{lcm}\bigl(\pi(2),\pi(5)\bigr)
=
\operatorname{lcm}(3,20)
=60.
π ( 10 ) = lcm ( π ( 2 ) , π ( 5 ) ) = lcm ( 3 , 20 ) = 60.
\operatorname{lcm}\bigl(\pi(2),\pi(5)\bigr)
\operatorname{lcm}(3,20)
=60.
60 60 60 来自 3 3 3 和 20 20 20 的最小公倍数。3 3 3 是模 2 2 2 的周期,20 20 20 是模 5 5 5 的周期。
类似地:
π ( 100 ) = lcm ( π ( 4 ) , π ( 25 ) ) = lcm ( 6 , 100 ) = 300.
\pi(100)
=
\operatorname{lcm}\bigl(\pi(4),\pi(25)\bigr)
=
\operatorname{lcm}(6,100)
=300.
π ( 100 ) = lcm ( π ( 4 ) , π ( 25 ) ) = lcm ( 6 , 100 ) = 300.
\operatorname{lcm}\bigl(\pi(4),\pi(25)\bigr)
\operatorname{lcm}(6,100)
=300.
π ( 1000 ) = lcm ( π ( 8 ) , π ( 125 ) ) = lcm ( 12 , 500 ) = 1500.
\pi(1000)
=
\operatorname{lcm}\bigl(\pi(8),\pi(125)\bigr)
=
\operatorname{lcm}(12,500)
=1500.
π ( 1000 ) = lcm ( π ( 8 ) , π ( 125 ) ) = lcm ( 12 , 500 ) = 1500.
\operatorname{lcm}\bigl(\pi(8),\pi(125)\bigr)
\operatorname{lcm}(12,500)
=1500.
π ( 6 ) = lcm ( π ( 2 ) , π ( 3 ) ) = lcm ( 3 , 8 ) = 24.
\pi(6)
=
\operatorname{lcm}\bigl(\pi(2),\pi(3)\bigr)
=
\operatorname{lcm}(3,8)
=24.
π ( 6 ) = lcm ( π ( 2 ) , π ( 3 ) ) = lcm ( 3 , 8 ) = 24.
\operatorname{lcm}\bigl(\pi(2),\pi(3)\bigr)
\operatorname{lcm}(3,8)
=24.
π ( 15 ) = lcm ( π ( 3 ) , π ( 5 ) ) = lcm ( 8 , 20 ) = 40.
\pi(15)
=
\operatorname{lcm}\bigl(\pi(3),\pi(5)\bigr)
=
\operatorname{lcm}(8,20)
=40.
π ( 15 ) = lcm ( π ( 3 ) , π ( 5 ) ) = lcm ( 8 , 20 ) = 40.
\operatorname{lcm}\bigl(\pi(3),\pi(5)\bigr)
\operatorname{lcm}(8,20)
=40.
π ( 30 ) = lcm ( π ( 2 ) , π ( 3 ) , π ( 5 ) ) = lcm ( 3 , 8 , 20 ) = 120.
\pi(30)
=
\operatorname{lcm}\bigl(\pi(2),\pi(3),\pi(5)\bigr)
=
\operatorname{lcm}(3,8,20)
=120.
π ( 30 ) = lcm ( π ( 2 ) , π ( 3 ) , π ( 5 ) ) = lcm ( 3 , 8 , 20 ) = 120.
\operatorname{lcm}\bigl(\pi(2),\pi(3),\pi(5)\bigr)
\operatorname{lcm}(3,8,20)
=120.
只要知道素数幂的 Pisano 周期,就能通过分解模数来计算许多合数的周期。
素数幂的周期
现在问题变成:当 p p p 是素数时,π ( p k ) \pi(p^k) π ( p k ) 是多少?
对 2 2 2 和 5 5 5 ,有两个常用规律:
π ( 2 k ) = 3 ⋅ 2 k − 1 , k ≥ 1.
\pi(2^k)=3\cdot2^{k-1},
\qquad k\ge1.
π ( 2 k ) = 3 ⋅ 2 k − 1 , k ≥ 1.
π ( 5 k ) = 4 ⋅ 5 k , k ≥ 1.
\pi(5^k)=4\cdot5^k,
\qquad k\ge1.
π ( 5 k ) = 4 ⋅ 5 k , k ≥ 1.
具体值是:
π ( 2 ) = 3 , π ( 4 ) = 6 , π ( 8 ) = 12 , π ( 16 ) = 24.
\pi(2)=3,
\quad
\pi(4)=6,
\quad
\pi(8)=12,
\quad
\pi(16)=24.
π ( 2 ) = 3 , π ( 4 ) = 6 , π ( 8 ) = 12 , π ( 16 ) = 24.
π ( 5 ) = 20 , π ( 25 ) = 100 , π ( 125 ) = 500 , π ( 625 ) = 2500.
\pi(5)=20,
\quad
\pi(25)=100,
\quad
\pi(125)=500,
\quad
\pi(625)=2500.
π ( 5 ) = 20 , π ( 25 ) = 100 , π ( 125 ) = 500 , π ( 625 ) = 2500.
于是,例如
π ( 1 0 6 ) = π ( 2 6 ⋅ 5 6 ) = lcm ( π ( 2 6 ) , π ( 5 6 ) ) .
\pi(10^6)
=
\pi(2^6\cdot5^6)
=
\operatorname{lcm}\bigl(\pi(2^6),\pi(5^6)\bigr).
π ( 1 0 6 ) = π ( 2 6 ⋅ 5 6 ) = lcm ( π ( 2 6 ) , π ( 5 6 ) ) .
\pi(2^6\cdot5^6)
\operatorname{lcm}\bigl(\pi(2^6),\pi(5^6)\bigr).
代入
π ( 2 6 ) = 3 ⋅ 2 5 = 96 , π ( 5 6 ) = 4 ⋅ 5 6 = 62500 ,
\pi(2^6)=3\cdot2^5=96,
\qquad
\pi(5^6)=4\cdot5^6=62500,
π ( 2 6 ) = 3 ⋅ 2 5 = 96 , π ( 5 6 ) = 4 ⋅ 5 6 = 62500 ,
得到
π ( 1 0 6 ) = lcm ( 96 , 62500 ) = 1500000.
\pi(10^6)=\operatorname{lcm}(96,62500)=1500000.
π ( 1 0 6 ) = lcm ( 96 , 62500 ) = 1500000.
对许多素数 p p p ,从 p p p 到 p k p^k p k 时,周期会按 p p p 的幂增长。严格证明需要更多数论工具,本书这里只保留这个观察和它的用法。
素数 p p p 的周期:和特征根有关
剩下的核心问题是:π ( p ) \pi(p) π ( p ) 是多少?
回忆第 8 章,Binet 公式来自特征方程
r 2 − r − 1 = 0.
r^2-r-1=0.
r 2 − r − 1 = 0.
在模 p p p 下,也可以研究这个方程。关键取决于 5 \sqrt5 5 是否存在于模 p p p 的世界里。
情况一:5 5 5 是模 p p p 的二次剩余
这表示存在某个整数 x x x ,使得
x 2 ≡ 5 ( m o d p ) .
x^2\equiv5\pmod p.
x 2 ≡ 5 ( mod p ) .
于是 5 \sqrt5 5 可以在模 p p p 下解释,φ \varphi φ 和 ψ \psi ψ 也可以在模 p p p 的有限域里解释。
这时 π ( p ) \pi(p) π ( p ) 与 φ \varphi φ 、ψ \psi ψ 在乘法群中的阶有关,因此周期会整除 p − 1 p-1 p − 1 。常写成:
π ( p ) ∣ p − 1 , 当 5 是模 p 的二次剩余。
\pi(p)\mid p-1,
\qquad
\text{当 }5\text{ 是模 }p\text{ 的二次剩余。}
π ( p ) ∣ p − 1 , 当 5 是模 p 的二次剩余。
二次互反律告诉我们,对奇素数 p ≠ 5 p\ne5 p = 5 ,5 5 5 是模 p p p 的二次剩余当且仅当
p ≡ ± 1 ( m o d 5 ) .
p\equiv\pm1\pmod5.
p ≡ ± 1 ( mod 5 ) .
例子:
π ( 11 ) = 10 = 11 − 1.
\pi(11)=10=11-1.
π ( 11 ) = 10 = 11 − 1.
π ( 19 ) = 18 = 19 − 1.
\pi(19)=18=19-1.
π ( 19 ) = 18 = 19 − 1.
π ( 29 ) = 14 , 14 ∣ 28 = 29 − 1.
\pi(29)=14,
\qquad
14\mid28=29-1.
π ( 29 ) = 14 , 14 ∣ 28 = 29 − 1.
情况二:5 5 5 是模 p p p 的非二次剩余
这表示
p ≡ ± 2 ( m o d 5 ) .
p\equiv\pm2\pmod5.
p ≡ ± 2 ( mod 5 ) .
这时 5 \sqrt5 5 不在模 p p p 的有限域里,而在它的二次扩域里。可以把它粗略理解为:要多引入一个类似 5 \sqrt5 5 的新元素,才能写出两个特征根。
在这种情况下,周期整除 2 ( p + 1 ) 2(p+1) 2 ( p + 1 ) :
π ( p ) ∣ 2 ( p + 1 ) , 当 5 是模 p 的非二次剩余。
\pi(p)\mid2(p+1),
\qquad
\text{当 }5\text{ 是模 }p\text{ 的非二次剩余。}
π ( p ) ∣ 2 ( p + 1 ) , 当 5 是模 p 的非二次剩余。
例子:
π ( 3 ) = 8 = 2 ( 3 + 1 ) .
\pi(3)=8=2(3+1).
π ( 3 ) = 8 = 2 ( 3 + 1 ) .
π ( 7 ) = 16 = 2 ( 7 + 1 ) .
\pi(7)=16=2(7+1).
π ( 7 ) = 16 = 2 ( 7 + 1 ) .
π ( 13 ) = 28 = 2 ( 13 + 1 ) .
\pi(13)=28=2(13+1).
π ( 13 ) = 28 = 2 ( 13 + 1 ) .
π ( 23 ) = 48 = 2 ( 23 + 1 ) .
\pi(23)=48=2(23+1).
π ( 23 ) = 48 = 2 ( 23 + 1 ) .
特殊情况:p = 5 p=5 p = 5
p = 5 p=5 p = 5 要单独处理。特征方程在模 5 5 5 下变成
r 2 − r − 1 ≡ r 2 − r + 4 ( m o d 5 ) .
r^2-r-1\equiv r^2-r+4\pmod5.
r 2 − r − 1 ≡ r 2 − r + 4 ( mod 5 ) .
而
r 2 − r + 4 ≡ ( r − 3 ) 2 ( m o d 5 ) .
r^2-r+4\equiv(r-3)^2\pmod5.
r 2 − r + 4 ≡ ( r − 3 ) 2 ( mod 5 ) .
所以特征方程在模 5 5 5 下有重根 r ≡ 3 ( m o d 5 ) r\equiv3\pmod5 r ≡ 3 ( mod 5 ) 。这和前面两种情况都不一样。
事实是:
π ( 5 ) = 20.
\pi(5)=20.
π ( 5 ) = 20.
它不是 p − 1 = 4 p-1=4 p − 1 = 4 或 p + 1 = 6 p+1=6 p + 1 = 6 的因子,而是
20 = 5 ( 5 − 1 ) .
20=5(5-1).
20 = 5 ( 5 − 1 ) .
这种重根现象会让周期行为变得特殊。
二次互反律的位置
这一节用到了二次互反律的一个特例:
5 是模 p 的二次剩余 ⟺ p ≡ ± 1 ( m o d 5 ) , p ≠ 5.
5\text{ 是模 }p\text{ 的二次剩余}
\Longleftrightarrow
p\equiv\pm1\pmod5,
\qquad p\ne5.
5 是模 p 的二次剩余 ⟺ p ≡ ± 1 ( mod 5 ) , p = 5.
本书不证明二次互反律。证明需要不少准备,属于数论中级内容。这里只用它的结论来分类素数 p p p ,从而判断 π ( p ) \pi(p) π ( p ) 的整除范围。
这也说明:斐波那契数列的模周期问题并不只是初等算术。鸽巢原理只能告诉我们「一定循环」,具体周期是多少,会牵涉素数的深层结构。
一个应用:快速计算 F n m o d m F_n\bmod m F n mod m
知道 Pisano 周期后,要算 F n m o d m F_n\bmod m F n mod m ,可以先把下标缩小:
F n m o d m = F n m o d π ( m ) m o d m .
F_n\bmod m=F_{n\bmod\pi(m)}\bmod m.
F n mod m = F n mod π ( m ) mod m .
例子:计算
F 1 0 18 m o d 10.
F_{10^{18}}\bmod10.
F 1 0 18 mod 10.
因为
π ( 10 ) = 60 ,
\pi(10)=60,
π ( 10 ) = 60 ,
所以只要算
1 0 18 m o d 60.
10^{18}\bmod60.
1 0 18 mod 60.
注意当 n ≥ 2 n\ge2 n ≥ 2 时,
1 0 n ≡ 40 ( m o d 60 ) .
10^n\equiv40\pmod{60}.
1 0 n ≡ 40 ( mod 60 ) .
因此
1 0 18 ≡ 40 ( m o d 60 ) .
10^{18}\equiv40\pmod{60}.
1 0 18 ≡ 40 ( mod 60 ) .
于是
F 1 0 18 m o d 10 = F 40 m o d 10.
F_{10^{18}}\bmod10=F_{40}\bmod10.
F 1 0 18 mod 10 = F 40 mod 10.
又因为
F 40 = 102334155 ,
F_{40}=102334155,
F 40 = 102334155 ,
所以
F 1 0 18 m o d 10 = 5.
F_{10^{18}}\bmod10=5.
F 1 0 18 mod 10 = 5.
这里不需要写出 F 1 0 18 F_{10^{18}} F 1 0 18 。这个数约有 2.09 × 1 0 17 2.09\times10^{17} 2.09 × 1 0 17 位十进制数字,根本不适合直接展开。
整除性的另一个用法
模周期还能帮助判断 F n F_n F n 何时被某个数整除。
模 m m m 下,F n ≡ 0 ( m o d m ) F_n\equiv0\pmod m F n ≡ 0 ( mod m ) 的位置会形成周期结构。最小的正下标通常称为 m m m 的 rank of apparition,记作 α ( m ) \alpha(m) α ( m ) 。它满足
α ( m ) ∣ π ( m ) .
\alpha(m)\mid\pi(m).
α ( m ) ∣ π ( m ) .
例如
α ( 5 ) = 5 ,
\alpha(5)=5,
α ( 5 ) = 5 ,
所以
5 ∣ F n ⟺ 5 ∣ n .
5\mid F_n
\Longleftrightarrow
5\mid n.
5 ∣ F n ⟺ 5 ∣ n .
又如
α ( 10 ) = 15 ,
\alpha(10)=15,
α ( 10 ) = 15 ,
所以 F n F_n F n 末位是 0 0 0 当且仅当 15 ∣ n 15\mid n 15 ∣ n 。例子是:
F 15 = 610 , F 30 = 832040 , F 45 = 1134903170.
F_{15}=610,
\quad
F_{30}=832040,
\quad
F_{45}=1134903170.
F 15 = 610 , F 30 = 832040 , F 45 = 1134903170.
它们的末位都是 0 0 0 。
本章小结
π ( 10 ) = 60 \pi(10)=60 π ( 10 ) = 60 并不神秘。它来自
lcm ( π ( 2 ) , π ( 5 ) ) = lcm ( 3 , 20 ) = 60.
\operatorname{lcm}\bigl(\pi(2),\pi(5)\bigr)
=
\operatorname{lcm}(3,20)=60.
lcm ( π ( 2 ) , π ( 5 ) ) = lcm ( 3 , 20 ) = 60.
\operatorname{lcm}(3,20)=60.
$$
中国剩余定理把模合数的周期拆成了模素数幂周期的最小公倍数。
素数 p p p 的 Pisano 周期由 5 5 5 是否为模 p p p 的二次剩余控制。若 p ≡ ± 1 ( m o d 5 ) p\equiv\pm1\pmod5 p ≡ ± 1 ( mod 5 ) ,周期整除 p − 1 p-1 p − 1 ;若 p ≡ ± 2 ( m o d 5 ) p\equiv\pm2\pmod5 p ≡ ± 2 ( mod 5 ) ,周期整除 2 ( p + 1 ) 2(p+1) 2 ( p + 1 ) ;p = 5 p=5 p = 5 是重根特殊情况。
Pisano 周期的实际用处是把很大的下标缩小。计算 F n m o d m F_n\bmod m F n mod m 时,可以先用 n m o d π ( m ) n\bmod\pi(m) n mod π ( m ) 替换 n n n ,再计算较小下标的斐波那契数。下一章我们换一个方向:用斐波那契数反过来表示任意整数。
第 19 章 每个整数都有自己的斐波那契写法
到上一章为止,我们把 F n F_n F n 当作"被算出来的数"和"被数出来的数"看过,还把它当作"整数"研究过它的整除性和模周期。
这一章换个方向:能不能用斐波那契数反过来表示任意整数?
听起来有点奇怪。1 , 2 , 3 , 5 , 8 , 13 , 21 , … 1, 2, 3, 5, 8, 13, 21, \ldots 1 , 2 , 3 , 5 , 8 , 13 , 21 , … 这串数怎么能表示所有正整数?比如 100 100 100 怎么用斐波那契数之和表示?
从十进制和二进制说起
我们已经习惯用十进制表示整数。100 = 1 ⋅ 1 0 2 + 0 ⋅ 1 0 1 + 0 ⋅ 1 0 0 100 = 1 \cdot 10^2 + 0 \cdot 10^1 + 0 \cdot 10^0 100 = 1 ⋅ 1 0 2 + 0 ⋅ 1 0 1 + 0 ⋅ 1 0 0 。每个位置上的数字是 0 0 0 到 9 9 9 ,按位权 1 0 k 10^k 1 0 k 累加。
二进制也类似,100 = 64 + 32 + 4 = 2 6 + 2 5 + 2 2 100 = 64 + 32 + 4 = 2^6 + 2^5 + 2^2 100 = 64 + 32 + 4 = 2 6 + 2 5 + 2 2 ,写成 110010 0 2 1100100_2 110010 0 2 。每个位置上是 0 0 0 或 1 1 1 。
这些表示系统有三个要素:基本元素(1 0 k 10^k 1 0 k 或 2 k 2^k 2 k )、每个元素的系数(0 0 0 到 9 9 9 或 0 , 1 0, 1 0 , 1 )、组合规则(按位相加)。
Zeckendorf 表示也用这种思路,但是基本元素换成斐波那契数。
Zeckendorf 表示是什么
先用一个例子建立直觉。100 100 100 怎么用斐波那契数之和表示?
按"贪心"策略:每一步取不超过剩余值的最大斐波那契数。
100 100 100 不超过的最大斐波那契数是 89 = F 11 89 = F_{11} 89 = F 11 。剩余 100 − 89 = 11 100 - 89 = 11 100 − 89 = 11 。
11 11 11 不超过的最大斐波那契数是 8 = F 6 8 = F_6 8 = F 6 。剩余 11 − 8 = 3 11 - 8 = 3 11 − 8 = 3 。
3 3 3 不超过的最大斐波那契数是 3 = F 4 3 = F_4 3 = F 4 。剩余 3 − 3 = 0 3 - 3 = 0 3 − 3 = 0 。结束。
所以 100 = 89 + 8 + 3 = F 11 + F 6 + F 4 100 = 89 + 8 + 3 = F_{11} + F_6 + F_4 100 = 89 + 8 + 3 = F 11 + F 6 + F 4 。
注意一个细节:F 11 , F 6 , F 4 F_{11}, F_6, F_4 F 11 , F 6 , F 4 在斐波那契数列里不相邻。下标 11 , 6 , 4 11, 6, 4 11 , 6 , 4 之间都隔了至少 2 2 2 。
这不是巧合。这是 Zeckendorf 表示的关键特征。
Zeckendorf 定理 :每个正整数都可以唯一表示为若干个不相邻 斐波那契数之和。这个定理通常以比利时数学家 Edouard Zeckendorf 命名;Zeckendorf 在 1972 年发表相关论文,类似结果在更早的文献中也已经出现。
"不相邻"的意思是:在斐波那契数列里,被选中的数两两之间至少隔一个数。例如 F 11 + F 6 + F 4 F_{11} + F_6 + F_4 F 11 + F 6 + F 4 可以,因为下标 11 , 6 , 4 11, 6, 4 11 , 6 , 4 之间隔了至少 2 2 2 。F 11 + F 10 F_{11} + F_{10} F 11 + F 10 不行,因为下标 11 , 10 11, 10 11 , 10 相邻。
注意我们用的是 F 2 = 1 , F 3 = 2 , F 4 = 3 , F 5 = 5 , F 6 = 8 , … F_2 = 1, F_3 = 2, F_4 = 3, F_5 = 5, F_6 = 8, \ldots F 2 = 1 , F 3 = 2 , F 4 = 3 , F 5 = 5 , F 6 = 8 , … ,跳过 F 1 = 1 F_1 = 1 F 1 = 1 (避免重复的 1 1 1 )。也就是说,Zeckendorf 表示用 { 1 , 2 , 3 , 5 , 8 , 13 , 21 , … } \{1, 2, 3, 5, 8, 13, 21, \ldots\} { 1 , 2 , 3 , 5 , 8 , 13 , 21 , … } 这一组数,1 1 1 只出现一次。
贪心算法为什么会停下
证明 Zeckendorf 定理分两半:存在性和唯一性。先看存在性。
存在性的证明本身就是构造性的,它给出贪心算法。我们对 n n n 归纳。
n = 1 n = 1 n = 1 :取 F 2 = 1 F_2 = 1 F 2 = 1 。一个数,没有不相邻问题。对。
假设对一切 k < n k < n k < n 都成立。要证 n n n 也能表示。
取最大的不超过 n n n 的斐波那契数,记作 F m F_m F m 。所以 F m ≤ n < F m + 1 F_m \le n < F_{m+1} F m ≤ n < F m + 1 。
如果 n = F m n = F_m n = F m ,直接用 F m F_m F m 表示,结束。
如果 n > F m n > F_m n > F m ,让 n ′ = n − F m n' = n - F_m n ′ = n − F m ,那么 0 < n ′ < n 0 < n' < n 0 < n ′ < n 。由归纳假设,n ′ n' n ′ 可以表示为不相邻斐波那契数之和:n ′ = F k 1 + F k 2 + ⋯ + F k r n' = F_{k_1} + F_{k_2} + \cdots + F_{k_r} n ′ = F k 1 + F k 2 + ⋯ + F k r ,其中 k 1 > k 2 > ⋯ > k r k_1 > k_2 > \cdots > k_r k 1 > k 2 > ⋯ > k r ,且 k i − k i + 1 ≥ 2 k_i - k_{i+1} \ge 2 k i − k i + 1 ≥ 2 。
那么 n = F m + F k 1 + ⋯ + F k r n = F_m + F_{k_1} + \cdots + F_{k_r} n = F m + F k 1 + ⋯ + F k r 。
要检查这套表示满足"不相邻"。我们要证 m − k 1 ≥ 2 m - k_1 \ge 2 m − k 1 ≥ 2 ,也就是 F m F_m F m 和 F k 1 F_{k_1} F k 1 不相邻。
因为 F k 1 ≤ n ′ = n − F m < F m + 1 − F m = F m − 1 F_{k_1} \le n' = n - F_m < F_{m+1} - F_m = F_{m-1} F k 1 ≤ n ′ = n − F m < F m + 1 − F m = F m − 1 。所以 F k 1 < F m − 1 F_{k_1} < F_{m-1} F k 1 < F m − 1 ,即 F k 1 ≤ F m − 2 F_{k_1} \le F_{m-2} F k 1 ≤ F m − 2 (因为斐波那契数列递增),即 k 1 ≤ m − 2 k_1 \le m - 2 k 1 ≤ m − 2 ,即 m − k 1 ≥ 2 m - k_1 \ge 2 m − k 1 ≥ 2 。对。
所以存在性成立。这个证明本身就是算法:贪心算法 。每次取最大的不超过剩余值的斐波那契数,递归处理。
为什么不会出现相邻项
贪心算法自动避免相邻项,这是它的关键性质。原因是上一步证明里那个不等式:F k 1 ≤ F m − 2 F_{k_1} \le F_{m-2} F k 1 ≤ F m − 2 。
具体说,如果当前步取了 F m F_m F m ,剩余值 n ′ < F m + 1 − F m = F m − 1 n' < F_{m+1} - F_m = F_{m-1} n ′ < F m + 1 − F m = F m − 1 。下一步取的最大斐波那契数 ≤ n ′ \le n' ≤ n ′ ,所以 ≤ F m − 2 \le F_{m-2} ≤ F m − 2 。下标差 ≥ 2 \ge 2 ≥ 2 。
这个性质自上而下传递,所以贪心算法给出的表示天然满足"不相邻"约束。
唯一性:补一个短证明
唯一性需要一个小引理。
引理。 在 F 2 , F 3 , … , F k − 1 F_2,F_3,\ldots,F_{k-1} F 2 , F 3 , … , F k − 1 中选若干项,要求下标不相邻。所有合法选择的和最多是
F k − 1 + F k − 3 + F k − 5 + ⋯ = F k − 1.
F_{k-1}+F_{k-3}+F_{k-5}+\cdots=F_k-1.
F k − 1 + F k − 3 + F k − 5 + ⋯ = F k − 1.
这个引理可以用递推证明。设 A k A_k A k 表示从 F 2 F_2 F 2 到 F k − 1 F_{k-1} F k − 1 中选不相邻项时能得到的最大和。若想让和尽量大,先选最大的 F k − 1 F_{k-1} F k − 1 。选了它,就不能选 F k − 2 F_{k-2} F k − 2 ,剩下最多只能从 F 2 F_2 F 2 到 F k − 3 F_{k-3} F k − 3 中继续选,所以
A k = F k − 1 + A k − 2 .
A_k=F_{k-1}+A_{k-2}.
A k = F k − 1 + A k − 2 .
从小值开始检查,A 2 = 0 = F 2 − 1 A_2=0=F_2-1 A 2 = 0 = F 2 − 1 ,A 3 = F 2 = 1 = F 3 − 1 A_3=F_2=1=F_3-1 A 3 = F 2 = 1 = F 3 − 1 。由递推可得
A k = F k − 1.
A_k=F_k-1.
A k = F k − 1.
现在证明唯一性。假设某个正整数有两种不同的 Zeckendorf 表示。把两边共同出现的项先删掉,剩下的两边仍然相等,而且没有共同项。设剩下两边中最大的下标是 k k k ,不妨设左边含有 F k F_k F k 。右边不能含有 F k F_k F k ,只能用下标小于 k k k 的项来补。
但根据刚才的引理,右边所有下标小于 k k k 且互不相邻的项,加起来最多是 F k − 1 F_k-1 F k − 1 ,不可能等于或超过左边的 F k F_k F k 。矛盾。
所以 Zeckendorf 表示唯一。
一个具体例子:从 1 到 20
把 1 1 1 到 20 20 20 都表示出来,让你直观感受这种"进制"。
n n n
Zeckendorf 表示
n n n
Zeckendorf 表示
1 1 1
F 2 F_2 F 2
11 11 11
F 6 + F 4 F_6 + F_4 F 6 + F 4
2 2 2
F 3 F_3 F 3
12 12 12
F 6 + F 4 + F 2 F_6 + F_4 + F_2 F 6 + F 4 + F 2
3 3 3
F 4 F_4 F 4
13 13 13
F 7 F_7 F 7
4 4 4
F 4 + F 2 F_4 + F_2 F 4 + F 2
14 14 14
F 7 + F 2 F_7 + F_2 F 7 + F 2
5 5 5
F 5 F_5 F 5
15 15 15
F 7 + F 3 F_7 + F_3 F 7 + F 3
6 6 6
F 5 + F 2 F_5 + F_2 F 5 + F 2
16 16 16
F 7 + F 4 F_7 + F_4 F 7 + F 4
7 7 7
F 5 + F 3 F_5 + F_3 F 5 + F 3
17 17 17
F 7 + F 4 + F 2 F_7 + F_4 + F_2 F 7 + F 4 + F 2
8 8 8
F 6 F_6 F 6
18 18 18
F 7 + F 5 F_7 + F_5 F 7 + F 5
9 9 9
F 6 + F 2 F_6 + F_2 F 6 + F 2
19 19 19
F 7 + F 5 + F 2 F_7 + F_5 + F_2 F 7 + F 5 + F 2
10 10 10
F 6 + F 3 F_6 + F_3 F 6 + F 3
20 20 20
F 7 + F 5 + F 3 F_7 + F_5 + F_3 F 7 + F 5 + F 3
观察一些规律:
每个数对应的 Zeckendorf 表示里没有相邻斐波那契数。例如 12 = F 6 + F 4 + F 2 12 = F_6 + F_4 + F_2 12 = F 6 + F 4 + F 2 ,下标 6 , 4 , 2 6, 4, 2 6 , 4 , 2 都隔了 2 2 2 。
每个数的 Zeckendorf 表示是唯一的。比如 20 20 20 不能表示成 F 7 + F 5 + F 3 F_7 + F_5 + F_3 F 7 + F 5 + F 3 之外的形式(满足不相邻)。
最大项决定数量级。例如 13 13 13 到 20 20 20 都以 F 7 F_7 F 7 开头,因为 F 7 = 13 F_7 = 13 F 7 = 13 是不超过它们的最大斐波那契数。
和二进制对比
性质
二进制
Zeckendorf
基本元素
2 2 2 的幂:1 , 2 , 4 , 8 , 16 , … 1, 2, 4, 8, 16, \ldots 1 , 2 , 4 , 8 , 16 , …
斐波那契数:1 , 2 , 3 , 5 , 8 , 13 , … 1, 2, 3, 5, 8, 13, \ldots 1 , 2 , 3 , 5 , 8 , 13 , …
每个元素的次数
0 0 0 或 1 1 1
0 0 0 或 1 1 1
约束
无
不相邻(相邻不能同时为 1 1 1 )
唯一性
是
是
进位规则
2 ⋅ 2 k = 2 k + 1 2 \cdot 2^k = 2^{k+1} 2 ⋅ 2 k = 2 k + 1
F i + F i + 1 = F i + 2 F_i + F_{i+1} = F_{i+2} F i + F i + 1 = F i + 2 ;重复项 2 F i = F i + 1 + F i − 2 2 F_i = F_{i+1} + F_{i-2} 2 F i = F i + 1 + F i − 2
Zeckendorf 表示比二进制"松"一点。基本元素更密(斐波那契数比 2 2 2 的幂多),约束更紧(不能相邻)。
Zeckendorf 表示通常比二进制表示长。原因是斐波那契数增长慢于 2 2 2 的幂,所以表示同一个数时,最高位位置往往更靠后。比如 100 100 100 的二进制需要 7 7 7 位,而它的 Zeckendorf 表示从 F 2 F_2 F 2 到 F 11 F_{11} F 11 需要 10 10 10 个位置。
Zeckendorf 表示和 Fibonacci coding 不是同一件事
这两个概念容易混淆,要区分清楚。
Zeckendorf 表示 是一种整数分解方式,它把整数写成若干个不相邻斐波那契数之和。它本身是一组数学对象,没有"编码"或"传输"的功能。
Fibonacci coding 是一种编码方法。它通常把 Zeckendorf 表示的位串反向,再在末尾追加一个 1 1 1 ,于是编码串以 11 11 11 结束。这个 11 11 11 是结束标记,让多个码字可以无分隔符地拼接。
因此,以 11 11 11 结束的是 Fibonacci coding 的编码串,不是 Zeckendorf 表示本身。
Zeckendorf 表示的加法规则:
两个 Zeckendorf 表示的数相加,先按位相加,允许系数为 0 , 1 , 2 0, 1, 2 0 , 1 , 2 ,再"归一化"处理。归一化有两类规则。第一类处理相邻项:F i + F i + 1 = F i + 2 F_i + F_{i+1} = F_{i+2} F i + F i + 1 = F i + 2 。第二类处理重复项:2 F i = F i + 1 + F i − 2 2 F_i = F_{i+1} + F_{i-2} 2 F i = F i + 1 + F i − 2 (因为 F i + 1 = F i + F i − 1 F_{i+1} = F_i + F_{i-1} F i + 1 = F i + F i − 1 且 F i = F i − 1 + F i − 2 F_i = F_{i-1} + F_{i-2} F i = F i − 1 + F i − 2 ,所以 F i + 1 + F i − 2 = F i + F i − 1 + F i − 2 = F i + F i = 2 F i F_{i+1} + F_{i-2} = F_i + F_{i-1} + F_{i-2} = F_i + F_i = 2 F_i F i + 1 + F i − 2 = F i + F i − 1 + F i − 2 = F i + F i = 2 F i )。小下标情形需要单独处理,因为 F i − 2 F_{i-2} F i − 2 当 i < 2 i < 2 i < 2 时会越界。反复应用这两类规则,直到没有相邻 1 1 1 也没有系数 2 2 2 ,得到合法的 Zeckendorf 表示。
比较两个 Zeckendorf 表示的数的大小,按位从高到低比较,第一个不同位决定大小。
Lucas 数列提醒我们什么
Zeckendorf 表示用斐波那契数。如果换成 Lucas 数列 L n L_n L n ,它同样满足递推
L n = L n − 1 + L n − 2 ,
L_n=L_{n-1}+L_{n-2},
L n = L n − 1 + L n − 2 ,
但初值变成
L 0 = 2 , L 1 = 1.
L_0=2,
\qquad
L_1=1.
L 0 = 2 , L 1 = 1.
Lucas 数列也可以用于构造表示系统,不过它需要额外约定,不能直接照搬 Zeckendorf 定理的表述。这里不展开 Lucas 表示理论,只把它作为提醒:换一个初值,许多看似相同的性质都会改变。
斐波那契的 Zeckendorf 表示唯一性依赖一套具体约定,包括使用 F 2 = 1 , F 3 = 2 , F 4 = 3 , … F_2=1,F_3=2,F_4=3,\ldots F 2 = 1 , F 3 = 2 , F 4 = 3 , … 作为基底,并禁止相邻下标同时出现。离开这些约定,表示系统就需要重新证明。
本章小结
Zeckendorf 定理:每个正整数都可以唯一表示为若干个不相邻斐波那契数之和。
证明思路:贪心算法给出存在性(每次取最大不超过剩余值的斐波那契数),斐波那契求和引理给出唯一性。
Zeckendorf 表示可以当作一种"进制",叫斐波那契进制。和二进制相比,基本元素更密(斐波那契数比 2 2 2 的幂多),约束更紧(不能相邻)。表示通常更长,但 Fibonacci coding 编码后会以 11 11 11 结束,可用于无分隔符压缩。
Zeckendorf 表示的唯一性是斐波那契特有的,不是所有线性递推都有。Lucas 表示需要额外约定,不能直接照搬 Zeckendorf 定理。
这件事让斐波那契数列在"表示整数"这个角度上有了新的角色。它不再只是"被算出来的数"或"被数出来的数",它本身可以作为"基底"反过来表示其他整数。这是斐波那契数列的另一面。
下一章我们换一个完全不同的场景:把斐波那契搬到概率里。看抛硬币这种最随机的场景里,斐波那契怎么会跑出来当计数员。
第 20 章 概率里的斐波那契:抛硬币不出现连续 HH
到这一章为止,斐波那契数列给人的印象偏向确定性。递推确定,算法确定,矩阵确定,整除性确定,模周期确定,Zeckendorf 表示也确定。
这一章把它带进概率里。概率看起来是不确定的:抛硬币出现正面还是反面,每次都不一样。但只要问题里出现「不允许某个局部模式」,计数结构就会重新出现,斐波那契数列也会跟着出现。
抛硬币不出现连续两个正面
抛 n n n 次公平硬币,每次结果是 H H H 或 T T T 。问:长度为 n n n 的所有结果串中,有多少个不出现连续两个 H H H ?
设这个数为 C n C_n C n 。
先看小值:
n = 0 n=0 n = 0 :只有空串,所以 C 0 = 1 C_0=1 C 0 = 1 。
n = 1 n=1 n = 1 :H , T H,T H , T ,所以 C 1 = 2 C_1=2 C 1 = 2 。
n = 2 n=2 n = 2 :H H , H T , T H , T T HH,HT,TH,TT HH , H T , T H , TT ,去掉 H H HH HH ,所以 C 2 = 3 C_2=3 C 2 = 3 。
n = 3 n=3 n = 3 :8 8 8 种里去掉 H H H , H H T , T H H HHH,HHT,THH HHH , HH T , T HH ,所以 C 3 = 5 C_3=5 C 3 = 5 。
n = 4 n=4 n = 4 :继续数下去,得到 C 4 = 8 C_4=8 C 4 = 8 。
于是
C 0 , C 1 , C 2 , C 3 , C 4 , … = 1 , 2 , 3 , 5 , 8 , …
C_0,C_1,C_2,C_3,C_4,\ldots=1,2,3,5,8,\ldots
C 0 , C 1 , C 2 , C 3 , C 4 , … = 1 , 2 , 3 , 5 , 8 , …
这就是斐波那契数列平移后的样子:
C n = F n + 2 .
C_n=F_{n+2}.
C n = F n + 2 .
为什么满足斐波那契递推
按最后一位分类。
如果最后一位是 T T T ,前面 n − 1 n-1 n − 1 位可以是任意合法串,方法数是 C n − 1 C_{n-1} C n − 1 。
如果最后一位是 H H H ,为了不出现 H H HH HH ,倒数第二位必须是 T T T 。也就是说,合法串末尾形如 T H TH T H ,前面 n − 2 n-2 n − 2 位可以是任意合法串,方法数是 C n − 2 C_{n-2} C n − 2 。
两类不重不漏,所以
C n = C n − 1 + C n − 2 .
C_n=C_{n-1}+C_{n-2}.
C n = C n − 1 + C n − 2 .
再加上 C 0 = 1 , C 1 = 2 C_0=1,C_1=2 C 0 = 1 , C 1 = 2 ,得到
C n = F n + 2 .
C_n=F_{n+2}.
C n = F n + 2 .
这和铺砖、楼梯、不连续子集、二进制串是同一个结构。把 H H H 当作 1 1 1 ,把 T T T 当作 0 0 0 ,不出现连续 H H H 就是不出现相邻 1 1 1 。
概率是多少
所有长度为 n n n 的硬币串共有 2 n 2^n 2 n 个,合法串有 C n = F n + 2 C_n=F_{n+2} C n = F n + 2 个。因此,不出现连续两个正面的概率为
P n = C n 2 n = F n + 2 2 n .
P_n=\frac{C_n}{2^n}=\frac{F_{n+2}}{2^n}.
P n = 2 n C n = 2 n F n + 2 .
一些具体值是:
P 1 = 2 2 = 1 , P 2 = 3 4 = 0.75 , P 3 = 5 8 = 0.625 , P 4 = 8 16 = 0.5 , P 5 = 13 32 ≈ 0.406 , P 10 = 144 1024 ≈ 0.141 , P 20 = 17711 1048576 ≈ 0.0169 , P 50 = F 52 2 50 ≈ 32951280099 1125899906842624 ≈ 2.93 × 1 0 − 5 .
\begin{aligned}
P_1&=\frac22=1,\\
P_2&=\frac34=0.75,\\
P_3&=\frac58=0.625,\\
P_4&=\frac8{16}=0.5,\\
P_5&=\frac{13}{32}\approx0.406,\\
P_{10}&=\frac{144}{1024}\approx0.141,\\
P_{20}&=\frac{17711}{1048576}\approx0.0169,\\
P_{50}&=\frac{F_{52}}{2^{50}}\approx\frac{32951280099}{1125899906842624}\approx2.93\times10^{-5}.
\end{aligned}
P 1 P 2 P 3 P 4 P 5 P 10 P 20 P 50 = 2 2 = 1 , = 4 3 = 0.75 , = 8 5 = 0.625 , = 16 8 = 0.5 , = 32 13 ≈ 0.406 , = 1024 144 ≈ 0.141 , = 1048576 17711 ≈ 0.0169 , = 2 50 F 52 ≈ 1125899906842624 32951280099 ≈ 2.93 × 1 0 − 5 .
随着 n n n 增大,P n P_n P n 趋向 0 0 0 。这符合直觉:抛得越久,越容易出现连续正面。
它趋向 0 0 0 的速度也能从斐波那契数列看出来。由于
F n + 2 ≈ φ n + 2 5 ,
F_{n+2}\approx\frac{\varphi^{n+2}}{\sqrt5},
F n + 2 ≈ 5 φ n + 2 ,
所以
P n ≈ φ n + 2 5 ⋅ 2 n = ( φ 2 ) n ⋅ φ 2 5 .
P_n\approx\frac{\varphi^{n+2}}{\sqrt5\cdot2^n}
=\left(\frac{\varphi}{2}\right)^n\cdot\frac{\varphi^2}{\sqrt5}.
P n ≈ 5 ⋅ 2 n φ n + 2 = ( 2 φ ) n ⋅ 5 φ 2 .
因为
φ 2 ≈ 0.809 < 1 ,
\frac{\varphi}{2}\approx0.809<1,
2 φ ≈ 0.809 < 1 ,
所以 P n P_n P n 以大约 0.80 9 n 0.809^n 0.80 9 n 的速度衰减。这里的 0.809 0.809 0.809 不是随便来的,它正是斐波那契增长率 φ \varphi φ 和全部硬币串增长率 2 2 2 的比值。
反过来:出现连续两个正面的概率
出现至少一次连续两个正面的概率是
1 − P n = 1 − F n + 2 2 n .
1-P_n=1-\frac{F_{n+2}}{2^n}.
1 − P n = 1 − 2 n F n + 2 .
比如:
n = 1 : 0 , n = 2 : 1 4 , n = 3 : 3 8 , n = 4 : 1 2 , n = 5 : 19 32 ≈ 0.594 , n = 10 : 1 − 0.141 ≈ 0.859 , n = 20 : 1 − 0.0169 ≈ 0.983.
\begin{aligned}
n=1 &: 0,\\
n=2 &: \frac14,\\
n=3 &: \frac38,\\
n=4 &: \frac12,\\
n=5 &: \frac{19}{32}\approx0.594,\\
n=10 &: 1-0.141\approx0.859,\\
n=20 &: 1-0.0169\approx0.983.
\end{aligned}
n = 1 n = 2 n = 3 n = 4 n = 5 n = 10 n = 20 : 0 , : 4 1 , : 8 3 , : 2 1 , : 32 19 ≈ 0.594 , : 1 − 0.141 ≈ 0.859 , : 1 − 0.0169 ≈ 0.983.
到 n = 20 n=20 n = 20 ,几乎肯定会出现连续两个正面。这有点像生日悖论:随着试验次数增加,某种局部碰撞会迅速变得很难避免。
Fibonacci coding 的密度
上一章提到 Fibonacci coding:它把整数编码成 0 / 1 0/1 0/1 串,除了作为结束标记的末尾 11 11 11 之外,主体部分不含相邻 1 1 1 。
这件事和本章的概率问题相关。长度为 n n n 的所有二进制串共有 2 n 2^n 2 n 个,其中不含相邻 1 1 1 的串只有 F n + 2 F_{n+2} F n + 2 个。所以如果把所有长度为 n n n 的二进制串看作空间,符合 Fibonacci coding 主体约束的串只占
F n + 2 2 n = P n .
\frac{F_{n+2}}{2^n}=P_n.
2 n F n + 2 = P n .
当 n n n 很大时,这个比例趋向 0 0 0 。也就是说,Fibonacci coding 的主体约束牺牲了编码密度,换来的是自同步和无分隔符读取的便利。
一个稍微复杂的例子:抛骰子不出现连续两个 6
把问题稍作推广。抛一枚均匀骰子 n n n 次,问不出现连续两个 6 6 6 的概率。
设合法序列数为 D n D_n D n 。
小值为:
D 0 = 1 , D 1 = 6 , D 2 = 35.
D_0=1,\qquad D_1=6,\qquad D_2=35.
D 0 = 1 , D 1 = 6 , D 2 = 35.
按末尾分类。
如果末尾不是 6 6 6 ,最后一位有 5 5 5 种选择,前面 n − 1 n-1 n − 1 位是任意合法序列,方法数是 5 D n − 1 5D_{n-1} 5 D n − 1 。
如果末尾是 6 6 6 ,倒数第二位不能是 6 6 6 ,所以末尾形如 X 6 X6 X 6 ,其中 X ∈ { 1 , 2 , 3 , 4 , 5 } X\in\{1,2,3,4,5\} X ∈ { 1 , 2 , 3 , 4 , 5 } 。前面 n − 2 n-2 n − 2 位是任意合法序列,方法数是 5 D n − 2 5D_{n-2} 5 D n − 2 。
因此
D n = 5 D n − 1 + 5 D n − 2 .
D_n=5D_{n-1}+5D_{n-2}.
D n = 5 D n − 1 + 5 D n − 2 .
概率为
P n = D n 6 n .
P_n=\frac{D_n}{6^n}.
P n = 6 n D n .
例如
P 2 = 35 36 ≈ 0.972.
P_2=\frac{35}{36}\approx0.972.
P 2 = 36 35 ≈ 0.972.
这个递推的特征方程是
r 2 = 5 r + 5 ,
r^2=5r+5,
r 2 = 5 r + 5 ,
也就是
r 2 − 5 r − 5 = 0.
r^2-5r-5=0.
r 2 − 5 r − 5 = 0.
较大根为
5 + 45 2 = 5 + 3 5 2 ≈ 5.854.
\frac{5+\sqrt{45}}2=\frac{5+3\sqrt5}{2}\approx5.854.
2 5 + 45 = 2 5 + 3 5 ≈ 5.854.
所以大致有
D n ≈ 5.85 4 n , P n ≈ ( 5.854 6 ) n ≈ 0.97 6 n .
D_n\approx5.854^n,
\qquad
P_n\approx\left(\frac{5.854}{6}\right)^n\approx0.976^n.
D n ≈ 5.85 4 n , P n ≈ ( 6 5.854 ) n ≈ 0.97 6 n .
这个例子说明,「不出现连续两个指定符号」不只在硬币里出现。换成更大的字母表,递推还在,只是系数和增长率变了。
一个推广方向:禁止更长的连续正面
如果不允许出现连续 k k k 个正面,合法序列数设为 D n , k D_{n,k} D n , k 。
按末尾连续 H H H 的个数分类,合法串可以以
T , H T , H H T , … , H k − 1 T
T,
\quad HT,
\quad HHT,
\quad \ldots,
\quad H^{k-1}T
T , H T , HH T , … , H k − 1 T
结尾。于是对 n ≥ k n\ge k n ≥ k 有
D n , k = D n − 1 , k + D n − 2 , k + ⋯ + D n − k , k .
D_{n,k}=D_{n-1,k}+D_{n-2,k}+\cdots+D_{n-k,k}.
D n , k = D n − 1 , k + D n − 2 , k + ⋯ + D n − k , k .
当 k = 2 k=2 k = 2 时,这就是斐波那契递推。当 k = 3 k=3 k = 3 时,得到 Tribonacci 型递推:
D n , 3 = D n − 1 , 3 + D n − 2 , 3 + D n − 3 , 3 ,
D_{n,3}=D_{n-1,3}+D_{n-2,3}+D_{n-3,3},
D n , 3 = D n − 1 , 3 + D n − 2 , 3 + D n − 3 , 3 ,
初值为
D 0 , 3 = 1 , D 1 , 3 = 2 , D 2 , 3 = 4.
D_{0,3}=1,\qquad D_{1,3}=2,\qquad D_{2,3}=4.
D 0 , 3 = 1 , D 1 , 3 = 2 , D 2 , 3 = 4.
下一章会继续这个方向,但问题换成「首次出现连续两个正面,平均要等多久」。那时我们不再固定抛 n n n 次,而是用状态法计算等待时间。
本章小结
抛硬币不出现连续两个正面的合法序列数是 F n + 2 F_{n+2} F n + 2 。它和铺砖、楼梯、不连续子集、二进制串共享同一个局部禁止结构。
概率
P n = F n + 2 2 n
P_n=\frac{F_{n+2}}{2^n}
P n = 2 n F n + 2
的衰减速度由 φ / 2 ≈ 0.809 \varphi/2\approx0.809 φ /2 ≈ 0.809 决定。这个数看起来属于概率,其实来自斐波那契数列的增长率。
推广到「不出现连续 k k k 个正面」会得到 k k k 阶递推。斐波那契只是其中 k = 2 k=2 k = 2 的情形。
第 21 章 首次 HH 的期望与 k k k -bonacci 推广
上一章算的是「抛 n n n 次硬币不出现连续两个正面」的概率。这一章换一个相关问题:一直抛硬币,直到第一次出现连续两个正面。平均要抛多少次?
设这个期望为 E E E 。这是一个经典随机过程问题,可以用状态法解。
状态法分析
考虑三种状态:
S 0 S_0 S 0 :当前还没有连续正面,且上一次不是 H H H ,或者刚开始。
S 1 S_1 S 1 :当前还没有连续正面,且上一次是 H H H 。
吸收态:已经出现 H H HH HH 。
设 E 0 E_0 E 0 是从 S 0 S_0 S 0 出发的期望步数,E 1 E_1 E 1 是从 S 1 S_1 S 1 出发的期望步数。
从 S 0 S_0 S 0 出发,抛一次后:以 1 / 2 1/2 1/2 概率到 S 1 S_1 S 1 ,以 1 / 2 1/2 1/2 概率回到 S 0 S_0 S 0 。所以
E 0 = 1 + 1 2 E 1 + 1 2 E 0 .
E_0=1+\frac{1}{2}E_1+\frac{1}{2}E_0.
E 0 = 1 + 2 1 E 1 + 2 1 E 0 .
从 S 1 S_1 S 1 出发,抛一次后:以 1 / 2 1/2 1/2 概率成功,期望剩余步数为 0 0 0 ;以 1 / 2 1/2 1/2 概率回到 S 0 S_0 S 0 。所以
E 1 = 1 + 1 2 ⋅ 0 + 1 2 E 0 .
E_1=1+\frac{1}{2}\cdot0+\frac{1}{2}E_0.
E 1 = 1 + 2 1 ⋅ 0 + 2 1 E 0 .
第一个方程化简为
E 0 = 2 + E 1 .
E_0=2+E_1.
E 0 = 2 + E 1 .
第二个方程是
E 1 = 1 + 1 2 E 0 .
E_1=1+\frac{1}{2}E_0.
E 1 = 1 + 2 1 E 0 .
代入得到
E 0 = 2 + 1 + 1 2 E 0 .
E_0=2+1+\frac{1}{2}E_0.
E 0 = 2 + 1 + 2 1 E 0 .
所以
1 2 E 0 = 3 , E 0 = 6.
\frac{1}{2}E_0=3,
\qquad
E_0=6.
2 1 E 0 = 3 , E 0 = 6.
再代回去:
E 1 = 1 + 1 2 ⋅ 6 = 4.
E_1=1+\frac{1}{2}\cdot6=4.
E 1 = 1 + 2 1 ⋅ 6 = 4.
因此,从开始状态出发,平均要抛 6 6 6 次才会首次出现连续两个正面。
验证一下
先看前几项是否合理。
抛 2 2 2 次时,首次 H H HH HH 出现在末尾的序列只有
概率是
抛 3 3 3 次时,末尾必须是 H H HH HH ,前面不能已经出现 H H HH HH 。合法序列是
概率是
抛 4 4 4 次时,首次 H H HH HH 出现在最后两位,合法序列是
T T H H 和 H T H H .
TTHH
\quad\text{和}\quad
HTHH.
TT HH 和 H T HH .
概率是
2 16 = 1 8 .
\frac2{16}=\frac18.
16 2 = 8 1 .
期望也可以写成无穷和:
E = ∑ n ≥ 2 n ⋅ P ( 首次 H H 在第 n 步 ) .
E=
\sum_{n\ge2}n\cdot
P(\text{首次 }HH\text{ 在第 }n\text{ 步}).
E = n ≥ 2 ∑ n ⋅ P ( 首次 HH 在第 n 步 ) .
这个求和可以算,但状态法更直接。前几项贡献是
2 ⋅ 1 4 + 3 ⋅ 1 8 + 4 ⋅ 1 8 + ⋯ .
2\cdot\frac14+3\cdot\frac18+4\cdot\frac18+\cdots.
2 ⋅ 4 1 + 3 ⋅ 8 1 + 4 ⋅ 8 1 + ⋯ .
最终结果是 6 6 6 ,与状态法一致。
一个直觉解释:为什么是 6
每次处在 S 0 S_0 S 0 ,要先等到一个 H H H 。抛出 H H H 的概率是 1 / 2 1/2 1/2 ,所以平均要等 2 2 2 步进入 S 1 S_1 S 1 。
进入 S 1 S_1 S 1 后,再抛一次。若抛出 H H H ,成功;若抛出 T T T ,回到 S 0 S_0 S 0 ,重新开始。
从 S 1 S_1 S 1 出发的期望是
E 1 = 1 2 ⋅ 1 + 1 2 ( 1 + E 0 ) = 1 + 1 2 E 0 .
E_1=\frac{1}{2}\cdot1+\frac{1}{2}(1+E_0)=1+\frac{1}{2}E_0.
E 1 = 2 1 ⋅ 1 + 2 1 ( 1 + E 0 ) = 1 + 2 1 E 0 .
从 S 0 S_0 S 0 出发则是
E 0 = 2 + E 1 .
E_0=2+E_1.
E 0 = 2 + E 1 .
代入仍然得到
E 0 = 6 , E 1 = 4.
E_0=6,
\qquad
E_1=4.
E 0 = 6 , E 1 = 4.
等待 H H HH HH 的过程里有「推进一步」和「失败重来」两个动作。状态法把这个结构写成方程。
推广:等到连续 k k k 个正面
把问题推广:一直抛硬币,直到第一次出现连续 k k k 个正面,也就是 H k H^k H k 。平均要抛几次?
这次状态更多:
S 0 , S 1 , S 2 , … , S k − 1 , S k .
S_0,S_1,S_2,\ldots,S_{k-1},S_k.
S 0 , S 1 , S 2 , … , S k − 1 , S k .
其中 S i S_i S i 表示当前末尾已有连续 i i i 个 H H H ,但还没有达到 k k k 个。S k S_k S k 是吸收态。
设 E i E_i E i 是从 S i S_i S i 出发的期望步数。对 0 ≤ i < k 0\le i<k 0 ≤ i < k ,有
E i = 1 + 1 2 E i + 1 + 1 2 E 0 ,
E_i=1+\frac{1}{2}E_{i+1}+\frac{1}{2}E_0,
E i = 1 + 2 1 E i + 1 + 2 1 E 0 ,
并且
从后往前解,可以得到通用公式:
E 0 = 2 k + 1 − 2.
E_0=2^{k+1}-2.
E 0 = 2 k + 1 − 2.
具体值如下:
k 等待模式 E 0 1 H 2 2 − 2 = 2 2 H H 2 3 − 2 = 6 3 H H H 2 4 − 2 = 14 4 H H H H 2 5 − 2 = 30 5 H H H H H 2 6 − 2 = 62
\begin{array}{c|c|c}
k&\text{等待模式}&E_0\\
\hline
1&H&2^{2}-2=2\\
2&HH&2^{3}-2=6\\
3&HHH&2^{4}-2=14\\
4&HHHH&2^{5}-2=30\\
5&HHHHH&2^{6}-2=62
\end{array}
k 1 2 3 4 5 等待模式 H HH HHH HHHH HHHHH E 0 2 2 − 2 = 2 2 3 − 2 = 6 2 4 − 2 = 14 2 5 − 2 = 30 2 6 − 2 = 62
所以等到连续 5 5 5 个正面,平均要抛 62 62 62 次。这个数字比 1 / ( 1 / 32 ) = 32 1/(1/32)=32 1/ ( 1/32 ) = 32 大,是因为很多看似接近成功的尝试会被一个 T T T 打断,然后重新开始。
概率和 k k k -bonacci 的关系
上一章讨论过「不出现连续 k k k 个正面」的合法序列数。令
D n , k = 长度为 n 的 H / T 串中,不出现连续 k 个 H 的串数 .
D_{n,k}=
\text{长度为 }n\text{ 的 }H/T\text{ 串中,不出现连续 }k\text{ 个 }H\text{ 的串数}.
D n , k = 长度为 n 的 H / T 串中,不出现连续 k 个 H 的串数 .
它满足 k k k 阶递推:
D n , k = D n − 1 , k + D n − 2 , k + ⋯ + D n − k , k .
D_{n,k}=D_{n-1,k}+D_{n-2,k}+\cdots+D_{n-k,k}.
D n , k = D n − 1 , k + D n − 2 , k + ⋯ + D n − k , k .
这一章的等待时间问题给出的是
E k = 2 k + 1 − 2.
E_k=2^{k+1}-2.
E k = 2 k + 1 − 2.
两件事看起来不同,但都来自同一个局部约束:「不能过早出现连续 k k k 个 H H H 」。
D n , k D_{n,k} D n , k 数的是前 n n n 次里有多少种合法序列。E k E_k E k 问的是首次不合法平均出现在什么时候。两者可以通过首次出现的位置联系起来:
E k = ∑ n ≥ k n ⋅ P ( 首次出现 H k 在第 n 步 ) .
E_k=
\sum_{n\ge k} n\cdot
P(\text{首次出现 }H^k\text{ 在第 }n\text{ 步}).
E k = n ≥ k ∑ n ⋅ P ( 首次出现 H k 在第 n 步 ) .
本书不展开这个概率分布的完整推导。这里保留关系即可:合法串计数给出 k k k -bonacci,等待时间给出 2 k + 1 − 2 2^{k+1}-2 2 k + 1 − 2 。
k k k -bonacci:要区分两个对象
这里容易混淆两个相关但不同的对象。
对象一:避免连续 k k k 个正面的合法串数
定义:
D n , k = 长度为 n 的 H / T 串中,不出现连续 k 个 H 的串数 .
D_{n,k}=
\text{长度为 }n\text{ 的 }H/T\text{ 串中,不出现连续 }k\text{ 个 }H\text{ 的串数}.
D n , k = 长度为 n 的 H / T 串中,不出现连续 k 个 H 的串数 .
初值是:
D 0 , k = 1 , D n , k = 2 n ( 1 ≤ n < k ) .
D_{0,k}=1,
\qquad
D_{n,k}=2^n\quad(1\le n<k).
D 0 , k = 1 , D n , k = 2 n ( 1 ≤ n < k ) .
因为当 n < k n<k n < k 时,不可能出现连续 k k k 个 H H H ,所有串都合法。
当 n ≥ k n\ge k n ≥ k 时,按末尾连续 H H H 的个数分类,得到
D n , k = D n − 1 , k + D n − 2 , k + ⋯ + D n − k , k .
D_{n,k}=D_{n-1,k}+D_{n-2,k}+\cdots+D_{n-k,k}.
D n , k = D n − 1 , k + D n − 2 , k + ⋯ + D n − k , k .
例如 k = 3 k=3 k = 3 时:
D 0 , 3 , D 1 , 3 , D 2 , 3 , D 3 , 3 , D 4 , 3 , … = 1 , 2 , 4 , 7 , 13 , …
D_{0,3},D_{1,3},D_{2,3},D_{3,3},D_{4,3},\ldots
=
1,2,4,7,13,\ldots
D 0 , 3 , D 1 , 3 , D 2 , 3 , D 3 , 3 , D 4 , 3 , … = 1 , 2 , 4 , 7 , 13 , …
1,2,4,7,13,\ldots
对象二:某种约定下的 k k k -bonacci 数列
不同书对 k k k -bonacci 的初值约定并不完全一样。常见的一种约定是
a n , k = a n − 1 , k + a n − 2 , k + ⋯ + a n − k , k ,
a_{n,k}=a_{n-1,k}+a_{n-2,k}+\cdots+a_{n-k,k},
a n , k = a n − 1 , k + a n − 2 , k + ⋯ + a n − k , k ,
并设
a 0 , k = a 1 , k = ⋯ = a k − 1 , k = 1.
a_{0,k}=a_{1,k}=\cdots=a_{k-1,k}=1.
a 0 , k = a 1 , k = ⋯ = a k − 1 , k = 1.
按这个约定:
k=2 (Fibonacci): 1, 1, 2, 3, 5, 8, 13, 21, ...
k=3 (Tribonacci): 1, 1, 1, 3, 5, 9, 17, 31, 57, ...
k=4 (Tetranacci): 1, 1, 1, 1, 4, 7, 13, 25, 49, ...
注意,这个标准 k k k -bonacci 和 D n , k D_{n,k} D n , k 不完全一样,因为初值不同。标准 k k k -bonacci 在 0 ≤ n < k 0\le n<k 0 ≤ n < k 时都取 1 1 1 ,而 D n , k D_{n,k} D n , k 在 1 ≤ n < k 1\le n<k 1 ≤ n < k 时取 2 n 2^n 2 n 。
本书在概率问题里使用的是 D n , k D_{n,k} D n , k 。提到 k k k -bonacci 时,应当明确所采用的初值约定。
哪些性质可以推广
许多结构可以从斐波那契推广到 k k k -bonacci,例如:
矩阵形式:用 k × k k\times k k × k 伴随矩阵表示状态转移;
闭式公式:用特征根的线性组合描述通项;
组合解释:用 1 , 2 , … , k 1,2,\ldots,k 1 , 2 , … , k 格砖铺走廊;
模周期:模 m m m 下的状态向量长度为 k k k ,状态空间上界为 m k m^k m k 。
但有些性质未必保留:
整除性:F m ∣ F n ⟺ m ∣ n F_m\mid F_n\Longleftrightarrow m\mid n F m ∣ F n ⟺ m ∣ n 这种结构依赖斐波那契的具体初值;
唯一表示:Zeckendorf 表示的唯一性是斐波那契的特殊性质;
素性:Fibonacci 素数的研究依赖斐波那契数列本身,不能简单搬到一般 k k k -bonacci 上。
所以,斐波那契数列是线性递推的一个清楚样本。它展示了哪些结构可以推广,也提醒我们:并非所有性质都会跟着保留。
一个稍微深的问题:Fibonacci 素数
最后看一个开放方向。
斐波那契数中有些是素数:
F 3 = 2 , F 4 = 3 , F 5 = 5 , F 7 = 13 , F 11 = 89 , F 13 = 233 , F 17 = 1597 , …
F_3=2,
\quad
F_4=3,
\quad
F_5=5,
\quad
F_7=13,
\quad
F_{11}=89,
\quad
F_{13}=233,
\quad
F_{17}=1597,
\ldots
F 3 = 2 , F 4 = 3 , F 5 = 5 , F 7 = 13 , F 11 = 89 , F 13 = 233 , F 17 = 1597 , …
这些数叫 Fibonacci 素数。
注意,下标 n n n 大多是素数,例外是 n = 4 n=4 n = 4 。这和第 16 章的整除性定理有关:当 m ≥ 3 m\ge3 m ≥ 3 时,
F m ∣ F n ⟺ m ∣ n .
F_m\mid F_n
\Longleftrightarrow
m\mid n.
F m ∣ F n ⟺ m ∣ n .
如果 n n n 是合数,写成 n = m k n=mk n = mk ,其中 m ≥ 3 m\ge3 m ≥ 3 ,那么
F m ∣ F n .
F_m\mid F_n.
F m ∣ F n .
这通常会让 F n F_n F n 成为合数。因此,若 F n F_n F n 是素数,则 n n n 必须是素数,或者是特殊下标 n = 4 n=4 n = 4 。
反过来不成立。比如
F 19 = 4181 = 37 × 113 ,
F_{19}=4181=37\times113,
F 19 = 4181 = 37 × 113 ,
虽然 19 19 19 是素数,但 F 19 F_{19} F 19 不是素数。
是否存在无穷多个 Fibonacci 素数?这是数论中的未解问题。本书不继续展开,只把它作为斐波那契数列通向更深数论的一个入口。
本章小结
等到首次出现连续两个正面,平均要抛 6 6 6 次硬币。一般地,等到首次出现连续 k k k 个正面的期望是
2 k + 1 − 2.
2^{k+1}-2.
2 k + 1 − 2.
不出现连续 k k k 个正面的合法序列数满足 k k k 阶线性递推。它和等待时间问题都来自同一个局部限制:连续 k k k 个 H H H 不能过早出现。
k k k -bonacci 保留了斐波那契的一部分结构,比如矩阵形式、闭式公式、组合解释和模周期。但整除性、唯一表示、素性等性质未必保留,需要分别讨论。
到这里,斐波那契数列在概率里的角色告一段落。尾声会把视角再放宽:哪些性质属于一般线性递推,哪些性质是斐波那契特有的。
尾声:斐波那契不是终点
这本书写完了。我们一起把斐波那契数列从 0, 1, 1, 2, 3, 5, 8, 13 这八个数开始,一路看下去。看到递归、动态规划、矩阵快速幂、Binet 公式、铺砖、图、整除性、Pisano 周期、抛硬币概率、Zeckendorf 表示、k k k -bonacci 推广。
回头看,做了什么?
我们没有"穷尽"斐波那契数列。还有大量内容没碰:Zeckendorf 表示的高阶应用、Fibonacci heap 数据结构、Fibonacci 码的工程实现、负指标下的 F − n F_{-n} F − n 、Fibonacci 素数的未解问题、Fibonacci 数列和黄金比例的几何关联、与 Lucas 序列的精细关系、模形式和 L 函数的深层链接。这些都可以单独写一本书。
我们做的,是另一种事:把同一个对象,反复换语言看。
换语言这件事
把全书"换语言"的时刻列一下:
第 1 章:从故事(兔子、楼梯、铺砖)换成递推。
第 2 章:从递推换成递归程序。
第 3 章:从递归程序换成调用树,分析复杂度。
第 4 章:从朴素递归换成记忆化表 / 自底向上迭代。
第 5 章:从一步一加法换成翻倍跳步(快速倍增)。
第 6 章:从代数恒等式换成矩阵快速幂。
第 7 章:从矩阵作为工具换成矩阵作为状态语言。
第 8 章:从整数加法换成特征根的指数(Binet 公式)。
第 9 章:从 Binet 公式的"5 \sqrt{5} 5 怎么进入"换成"5 \sqrt{5} 5 怎么消失"。
第 10 章:从被算出来的数换成被数出来的对象(铺砖、楼梯)。
第 11 章:从铺砖换成更多计数外衣(子集、二进制串)。
第 12 章:从一维位置序列换成图上的点和线。
第 13 章:从恒等式换成代数证法。
第 14 章:从代数证法换成组合证法。
第 15 章:从数列本身换成生成函数。
第 16 章:从数列的值换成下标的算术结构。
第 17 章:从无限序列换成有限状态系统的循环。
第 18 章:从 Pisano 周期的现象换成它的素数结构。
第 19 章:从斐波那契数作为"被表示的对象"换成作为"表示的基底"。
第 20 章:从确定性问题换成概率里的合法计数。
第 21 章:从概率的"避免模式"换成"等待首次模式",推广到 k k k -bonacci。
每一次换语言,都看到一些新的东西。代数看见递推结构。算法看见子问题重叠。矩阵看见线性变换。闭式看见增长模式。组合看见计数对象。图论看见形状。数论看见算术镜像。模运算看见有限状态。概率看见合法路径。一般化看见一整族同类问题。
每一次换语言,也都"漏掉"一些东西。代数看不见计算复杂度。算法看不见组合意义。矩阵看不见闭式的简洁。闭式看不见整除性。组合看不见模周期。图论看不见增长速度。数论看不见算法效率。模运算看不见具体大值。概率看不见确定性结构。一般化看不见斐波那契特有的性质。
没有一种语言是"最好的"。每种语言适合回答某类问题,不适合回答另一类。
学数学的一个重要部分,就是学会在不同语言之间切换。看见一个问题,先问"这个问题在哪种语言里最自然"。如果原问题在代数里,但是组合解释更清楚,那就翻译到组合。如果原问题在算法里,但是矩阵形式更紧凑,那就翻译到矩阵。如果原问题在整数里,但是特征根在扩展域里更顺,那就翻译到扩展域。
这种"换语言"的意识,比任何具体的技巧都重要。技巧可以查,意识必须练。
一个最后的例子
收尾前,再看一个换语言的例子。
考虑恒等式:
F 2 n = F n ⋅ ( 2 F n + 1 − F n ) .
F_{2n} = F_n \cdot (2 F_{n+1} - F_n).
F 2 n = F n ⋅ ( 2 F n + 1 − F n ) .
第 5 章我们推过它。当时用加法公式 F n + m = F n + 1 F m + F n F m − 1 F_{n+m} = F_{n+1} F_m + F_n F_{m-1} F n + m = F n + 1 F m + F n F m − 1 ,让 m = n m = n m = n ,再做一点代数变形。
第 7 章矩阵视角下,这个恒等式是 ( M n ) 2 = M 2 n (M^n)^2 = M^{2n} ( M n ) 2 = M 2 n 的右上角 entry。具体来说:
M n = ( F n + 1 F n F n F n − 1 ) ,
M^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix},
M n = ( F n + 1 F n F n F n − 1 ) ,
( M n ) 2 = ( F n + 1 2 + F n 2 F n + 1 F n + F n F n − 1 ⋯ ⋯ ) ,
(M^n)^2 = \begin{pmatrix} F_{n+1}^2 + F_n^2 & F_{n+1} F_n + F_n F_{n-1} \\ \cdots & \cdots \end{pmatrix},
( M n ) 2 = ( F n + 1 2 + F n 2 ⋯ F n + 1 F n + F n F n − 1 ⋯ ) ,
M 2 n = ( F 2 n + 1 F 2 n F 2 n F 2 n − 1 ) .
M^{2n} = \begin{pmatrix} F_{2n+1} & F_{2n} \\ F_{2n} & F_{2n-1} \end{pmatrix}.
M 2 n = ( F 2 n + 1 F 2 n F 2 n F 2 n − 1 ) .
对比右上角:F 2 n = F n + 1 F n + F n F n − 1 = F n ( F n + 1 + F n − 1 ) = F n ( 2 F n + 1 − F n ) F_{2n} = F_{n+1} F_n + F_n F_{n-1} = F_n (F_{n+1} + F_{n-1}) = F_n (2 F_{n+1} - F_n) F 2 n = F n + 1 F n + F n F n − 1 = F n ( F n + 1 + F n − 1 ) = F n ( 2 F n + 1 − F n ) 。
第 10 章组合视角下,这个恒等式可以解释为:长度 2 n − 1 2n-1 2 n − 1 走廊的铺法数 = 按中间位置 n n n 的状态分类,三类铺法数之和。三类分别是"中间是 1 格砖"、"中间是 2 格砖跨过 n − 1 n-1 n − 1 和 n n n "、"中间是 2 格砖跨过 n n n 和 n + 1 n+1 n + 1 "。三类加起来给出 F n 2 + 2 F n F n − 1 = F n ( F n + 2 F n − 1 ) = F n ( 2 F n + 1 − F n ) F_n^2 + 2 F_n F_{n-1} = F_n (F_n + 2 F_{n-1}) = F_n (2 F_{n+1} - F_n) F n 2 + 2 F n F n − 1 = F n ( F n + 2 F n − 1 ) = F n ( 2 F n + 1 − F n ) 。
第 8 章闭式视角下,这个恒等式变成:
φ 2 n − ψ 2 n 5 = φ n − ψ n 5 ⋅ ( 2 ⋅ φ n + 1 − ψ n + 1 5 − φ n − ψ n 5 ) .
\frac{\varphi^{2n} - \psi^{2n}}{\sqrt{5}} = \frac{\varphi^n - \psi^n}{\sqrt{5}} \cdot \left( 2 \cdot \frac{\varphi^{n+1} - \psi^{n+1}}{\sqrt{5}} - \frac{\varphi^n - \psi^n}{\sqrt{5}} \right).
5 φ 2 n − ψ 2 n = 5 φ n − ψ n ⋅ ( 2 ⋅ 5 φ n + 1 − ψ n + 1 − 5 φ n − ψ n ) .
化简后两边都是 ( φ 2 n − ψ 2 n ) / 5 (\varphi^{2n} - \psi^{2n}) / \sqrt{5} ( φ 2 n − ψ 2 n ) / 5 ,所以相等。这个证明看起来"显然",但是依赖于 φ \varphi φ 和 ψ \psi ψ 满足特征方程 r 2 = r + 1 r^2 = r + 1 r 2 = r + 1 。
第 15 章生成函数视角下,这个恒等式可以从 G ( x 2 ) G(x^2) G ( x 2 ) 和 G ( x ) G(x) G ( x ) 的关系推出来,但是推导比较繁琐。
同一个恒等式,至少四种证法:代数(用加法公式)、矩阵(看 M 2 n M^{2n} M 2 n 的 entry)、组合(按中间位置分类计数)、闭式(用 φ , ψ \varphi, \psi φ , ψ 的代数性质)。
四种证法给出四种理解:
代数证法告诉你这个恒等式来自加法公式,加法公式本身来自递推的线性结构。
矩阵证法告诉你这个恒等式是"矩阵平方"的一个 entry,矩阵平方对应"翻倍"。
组合证法告诉你这个恒等式是"按中间位置分类计数"的结果。
闭式证法告诉你这个恒等式是特征方程 r 2 = r + 1 r^2=r+1 r 2 = r + 1 的代数后果。
四种理解加起来,比任何一种都更完整。这就是换语言的力量。同一条事实,从四个角度看,看到四个不同的"为什么"。
这本书没有做的事
最后说几句这本书没有做的事。
没有把黄金比例神秘化。φ \varphi φ 出现在 Binet 公式里,因为它满足 r 2 = r + 1 r^2=r+1 r 2 = r + 1 。这个方程的解是 (1 ± \pm ± 5 \sqrt{5} 5 )/2。φ \varphi φ 不是宇宙密码,它是一个二次方程的较大根。所有"φ \varphi φ 在自然界无处不在"的说法,要么是把这个代数事实夸大了,要么是把别的比例(比如 1.4, 1.5)也算成"接近 φ \varphi φ "凑出来的。
没有把斐波那契数列神秘化。斐波那契数列确实反复出现在不同数学分支里,但这种反复有具体原因。它对应最简单的二阶线性递推,这种结构在很多场景里都会自然出现。这是结构必然。
没有把数学写成知识点清单。这本书的章节顺序不是按"知识点难度"排的,而是按"换语言的顺序"排的。每一章换一种语言,看同一种结构在新语言里是什么样。这种顺序牺牲了某些系统性(比如恒等式没有集中列在某一章,而是散落在各章里),但换来了"换语言"这条主线的清晰。
没有承诺看完就懂数学。看完这本书,你不会变成数学家,也不会变成算法专家。但是你应该比以前更习惯一件事:看见一个数学对象,会问"它能在几种语言里被描述"。这种习惯比任何具体知识都重要。
一个最后的画面
回到开头的画面。一串很乖的整数 0, 1, 1, 2, 3, 5, 8, 13, …。
它本来只是"前两项加起来得下一项"。但是多看几眼,它开始到处开门。
我们推开过 21 扇门。
第一扇门里,它从兔子、楼梯、铺砖三个故事里分别长出来。
第二扇门里,它被翻译成一段递归程序。
第三扇门里,那棵调用树长得像斐波那契分形。
第四扇门里,那棵树被一张备忘录压扁成一条链。
第五扇门里,加法变成了乘法,k 步压成 1 步。
第六扇门里,矩阵帮忙做同样的跳步。
第七扇门里,矩阵变成了状态变化的语言。
第八扇门里,5 \sqrt{5} 5 出现了。
第九扇门里,5 \sqrt{5} 5 消失了。
第十扇门里,它变成了五块真实的砖头。
第十一扇门里,它变成了子集和串。
第十二扇门里,它被画成了点和线。
第十三扇门里,它的恒等式被代数证明。
第十四扇门里,它的恒等式被计数证明。
第十五扇门里,它整个数列被压缩成一个分式。
第十六扇门里,它的下标开始说话。
第十七扇门里,它的余数开始循环。
第十八扇门里,循环长度被中国剩余定理拆开。
第十九扇门里,它反过来表示任意整数。
第二十扇门里,它跑到抛硬币里。
第二十一扇门里,它推广到 k k k -bonacci。
门开了 21 扇。数列没变。
变的是我们看它的方式。
数学从来不只是一种写法。同一个对象,可以被无数种语言描述。每一种语言都看见一种结构,每一种结构都揭示一种性质。学数学的乐趣,一大半就在这种"换一种说法,看见一种新东西"的瞬间里。
斐波那契数列是这个乐趣的最便宜入口。它不需要任何前置知识,只需要加法。但它能撑起一整本关于"数学如何换语言"的书。
下一串你想去看的数是什么?
也许你会从某个看起来更简单的对象开始,比如素数、平方数、Catalan 数、二项式系数。也许你会从某个看起来更复杂的对象开始,比如 Riemann zeta 函数、模形式、随机矩阵。
无论从哪里开始,记住一件事:
别只用一种语言看它。
试试用代数看它。用算法看它。用矩阵看它。用组合看它。用图论看它。用数论看它。用模运算看它。用概率看它。用几何看它。用拓扑看它。用任何你会的语言看它。
每种语言都会让你看见一点别的语言看不见的东西。
这就是数学。
这就是我们为什么花 21 章只看一串数。
谢谢读到这里的你。