seegongsik
我的单词本
算法

把解过的答案再用一遍

你用递归解过斐波那契吗?要算 fib(5),就得叫 fib(4) 和 fib(3),fib(4) 又叫 fib(3) 和 fib(2)。可 fib(2) 这儿那儿地老冒出来。同一个东西被一遍遍重解。要是把解过的答案记在哪儿呢?

01

又解一遍同样的

递归把大问题
劈成小问题来解。
斐波那契正是这样。
fib(5) 叫 fib(4) 和 fib(3),
fib(4) 又叫 fib(3) 和 fib(2)。
可仔细一看,
fib(2) 在这枝那枝上
老是又冒出来。
在下面的树里
点一个节点试试。

用递归解 fib(5),就摊开成这么一棵树。点一个节点试试。
随便点一个节点。同一个小问题藏在树的各处。

同一个小问题在树里到处又冒出来。(点节点 → 同值全部高亮)

看见了吧?
同一个 fib(2)
不止解一次,而是解好几次。
fib(3) 也一样。
n 一大,
这种重复就猛涨。
把已经解好的答案
一遍遍重解,
谁看都觉得浪费。

02

把答案记下来

办法出乎意料地简单。
把解过的答案
记在便条本上。
下次又要同一个,
就别重算,
直接从便条上拿出来用。
只算头回见的,
见过的就拿出来。
按按钮,
一个个处理调用。

同样的调用又来了。点一下处理下一个调用。
fib(2)
fib(3)
fib(2)
fib(4)
fib(2)
fib(3)
便条本
还是空的
按一下,调用就一个个进来。头回见的就算,见过的就从便条上拿。

头回见的就算了记下,见过的就从便条上拿。(这就是记忆化)

计算只在头一回。
其余的全
从便条上拿出来用,
所以把同一个东西解两遍这事
彻底没了。
像这样把解过的答案
记下来再用,
就叫记忆化。
名字唬人,
里头其实就是“记下来”。

03

从底下往上填表

同一个想法
也可以反过来做。
不从顶上往下劈,
而是从最底下的小值
把表往上填。
fib(0) 和 fib(1) 直接就知道。
把这俩加起来得 fib(2),
fib(1) 加 fib(2) 得 fib(3)。
下面的格子已经写好了,
上面的格子只管相加就行。

反过来,从最底下的 0 和 1 开始把表往上填。
fib(0)0一开始就知道
fib(1)1一开始就知道
fib(2)?
fib(3)?
fib(4)?
fib(5)?
fib(6)?
fib(0)=0、fib(1)=1 我们直接就知道。从这儿往上一格一格填。

从 0、1 起一格格往上填。(下一格 = 紧下方两格之和 · 自底向上)

这种方式连便条
都不用。
表本身就是便条。
从下到上
顺一遍,
压根没有重解。
要是记忆化是
“要用时记下来”,
这个就是“提前全记下来”。
两者骨子里一样。

04

快得多了

那到底快多少呢?
就靠记下来这一手,
变化大得惊人。
纯递归
连重复的也全解,
所以 n 越大,
活儿就像爆炸一样涨。
记忆化每格只解一次,
只跟 n 成正比。
把 n 调大,比一比两者。

把 n 调大,比一比两种方式的工作量。
n = 5
纯递归15
记忆化6
算 fib(5),纯递归要解 15 次,记忆化只解 6 次。整整差了 3 倍。n 越大,差距越爆炸。

同一个 n 下解的次数。(纯递归=指数级爆炸 · 记忆化=与 n 成正比的线性)

差别看见了吧?
n 才大一点点,
纯递归的柱子
就像要冲出屏幕一样窜上去。
记忆化的柱子
几乎纹丝不动。
不把同一个答案再解一遍,
就这一点,
分开了慢与快。

05

小结

动态规划
名字唬人,
里头却就三步。
察觉自己在又解一遍同样的,
把解过的答案记下来,
再用就快得多。
把下面的卡片一个个点开,
小结这三步。

动态规划就三步。一个个点亮来看。
1
又解一遍
递归把同一个小问题反复地解
2
记下来
把解过的答案记在便条上再拿出来用
3
快得多
指数变成线性,差距爆炸
按按钮,把三步一个个点亮。

又解一遍 → 记下来 → 快得多。(回收分治不重叠、故无需便条这点)

上回的分治里,
劈出来的块互不重叠,
所以没什么可记的。
那儿正是岔路口。
当块重叠时,
也就是同一个小问题
老是又冒出来时,
记下来这一手
才终于改变了一切。

一句话总结递归常常把同一个小问题反复地重解。光看斐波那契的树就知道,fib(2) 到处又冒出来。把解过的答案记在便条上再用,就不会把同一个东西解两遍了。这就是记忆化。反过来,从最底下的小值开始把表往上填,也是同一个想法。这么一来,原本指数级地慢,就变成线性,快得多了。分治劈出来的块互不重叠,所以没什么可记的,可一旦重叠,记下来这一手就改变一切。
算法
如果有帮助,请支持我们