算法与正确性
复习
- 一次网络请求的完整旅程(总装):从输入网址到页面出现,是一次跨越全部分层的旅程
- 数据为什么需要组织:同一批数据可以有多种组织方式,各有优劣
- 抽象数据类型:先规定能做什么,再决定怎么做
TL;DR
- 算法是有限、明确、可执行的步骤,用来解决一类问题
- 合格的算法要正确,也要能在可接受的时间内完成
- 正确性需要论证,不能只靠“跑通几个例子”
- 把问题本身说清楚,是设计算法的前提
正文
数据结构解决“数据怎么摆”,接下来自然要问“步骤怎么走”。后者,就是算法(algorithm)。
算法是什么
一个算法,通常要满足几条朴素的品质:
- 有限:步骤是有限的,且一定会在有限步内结束
- 明确:每一步都清晰无歧义
- 可执行:每一步都是能真正做出来的操作
- 有输入输出:对给定输入,产生确定的输出
注意“有限”这一条有多重要。如果一段步骤永远停不下来,它就称不上算法——无论它看起来多聪明。
一个经典例子
求两个数的最大公约数,有个流传两千多年的方法——辗转相除法(欧几里得算法):
当 b ≠ 0:
(a, b) ← (b, a 除以 b 的余数)
答案就是最后的 a
比如求 48 和 18 的最大公约数:
48 ÷ 18余 12,变成(18, 12)18 ÷ 12余 6,变成(12, 6)12 ÷ 6余 0,变成(6, 0)- 此时
b = 0,答案是 6
步骤简单,结果正确。
为什么它一定对
可“跑出来对”和“一定对”是两回事。我们凭什么相信,这个方法对任何两个数都成立?
关键在于一个事实:两个数的公约数,和“较小数”与“两数相除的余数”的公约数,是完全一样的。 也就是说,把 (a, b) 换成 (b, a mod b),不会漏掉也不会多出任何一个公约数。既然每一步都保持“公约数集合不变”,而过程又在不断变小、必然终止,那么最终剩下的那个数,就是我们要求的最大公约数。
你看,正确的算法,背后往往有一条能讲清楚的理由,而不是碰巧试对了。
跑通例子不算证明
这一点值得多说一句。我们之前在讲测试时提过:测试能发现错误,却不能证明没有错误。
算法也是一样。你拿十几个输入跑一遍都通过了,只能说明这十几个情况没问题,不能保证下一个输入不出岔子。真正让人放心的,是像上面那样,把“为什么它对”讲明白。
当然,正确只是及格线。一个算法即使正确,如果慢到跑不完,也派不上用场。那么,该怎样衡量“快慢”?下一章开始,我们就来解决这个问题。
思考题 1
为什么“在有限步内结束”是算法的必要条件?
思考题 2
一个程序“跑通了几个例子”,能证明它正确吗?如果不能,那要怎样才更让人信服?
小结
知识点
- 算法是有限、明确、可执行的步骤
- 正确之外,还要考虑运行时间
- 正确性依赖论证,而非枚举用例
- 辗转相除法体现了“不变式 + 终止”的证明思路
参考资料
- Wikipedia(zh):算法:解决问题的有限步骤序列
- Wikipedia(zh):欧几里得算法:求最大公约数的经典方法
思考题答案(仅供参考)
思考题 1
因为算法要能给出答案。如果步骤可能永远进行下去,就永远得不到结果,也就无法解决问题。有限且必然终止,是“这个方法真的能用”的基本保障。
思考题 2
不能。枚举再多的用例,也只覆盖了有限情况,无法排除没试过的输入出错。更让人信服的做法是给出论证:说明每一步都保持某种正确性质(不变式),并证明过程一定终止,从而对全部合法输入都成立。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪