用精确对象描述问题

集合关心成员而不关心重复次序;序列保留位置;映射把定义域中的元素对应到值。问题需要计数时,先确认处理的是不同对象、不同位置还是不同排列,否则很容易重复计算。

命题可以判断真假,蕴含 P ⇒ Q 表示 P 为真时 Q 必须为真。逆命题并不自动成立。证明“所有输入都正确”需要覆盖完整输入范围;几个成功例子只能验证这些例子。

归纳证明怎样落到程序

假设一个循环依次处理列表前缀。要证明前缀和算法正确,可以规定:处理前 k 个元素后,变量 total 等于这 k 个元素之和。

  1. k=0 时,空和为 0,初始化成立。
  2. 如果处理 k 个元素时成立,加上下一个元素后,就得到前 k+1 个元素之和。
  3. 循环结束时 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]

图与依赖关系

图由顶点和边构成。无向边表示对称关系,有向边表达方向;权重可以表示距离或代价,但必须定义单位与允许范围。学习前置依赖可以建成有向图,若出现环,就无法按依赖顺序全部开始,需要拆开互相依赖的概念。

树是受约束的图,不能把适用于树的算法不加检查地用于一般图。遍历一般图时通常需要记录访问状态,避免重复进入或无限循环。

练习与面试自查

为“排序后取中间项总能得到众数”寻找反例。再给四个知识点建立前置依赖,手工找出一种合法顺序,并加入一条会形成环的边。

完成时应能区分证明、反例和测试,并能解释计数中“有序还是无序、允许重复还是不允许重复”的条件。

参考资料