用精确对象描述问题
集合关心成员而不关心重复次序;序列保留位置;映射把定义域中的元素对应到值。问题需要计数时,先确认处理的是不同对象、不同位置还是不同排列,否则很容易重复计算。
命题可以判断真假,蕴含 P ⇒ Q 表示 P 为真时 Q 必须为真。逆命题并不自动成立。证明“所有输入都正确”需要覆盖完整输入范围;几个成功例子只能验证这些例子。
归纳证明怎样落到程序
假设一个循环依次处理列表前缀。要证明前缀和算法正确,可以规定:处理前 k 个元素后,变量 total 等于这 k 个元素之和。
- k=0 时,空和为 0,初始化成立。
- 如果处理 k 个元素时成立,加上下一个元素后,就得到前 k+1 个元素之和。
- 循环结束时 k=n,因此得到整个列表的和。
def prefix_sums(values):
result = [0]
for value in values:
result.append(result[-1] + value)
return result
assert prefix_sums([2, -1, 4]) == [0, 2, 1, 5]
图与依赖关系
图由顶点和边构成。无向边表示对称关系,有向边表达方向;权重可以表示距离或代价,但必须定义单位与允许范围。学习前置依赖可以建成有向图,若出现环,就无法按依赖顺序全部开始,需要拆开互相依赖的概念。
树是受约束的图,不能把适用于树的算法不加检查地用于一般图。遍历一般图时通常需要记录访问状态,避免重复进入或无限循环。
练习与面试自查
为“排序后取中间项总能得到众数”寻找反例。再给四个知识点建立前置依赖,手工找出一种合法顺序,并加入一条会形成环的边。
完成时应能区分证明、反例和测试,并能解释计数中“有序还是无序、允许重复还是不允许重复”的条件。