1.1 计算机算法的历史

计算机算法的历史可以追溯到1944年,冯·诺依曼设计了第一台真正意义上的通用计算机EDVAC,这也是冯诺依曼体系结构的首次实现。这一突破为现代计算机科学奠定了基础。

在算法研究领域,高德纳的贡献:

  • 大O表示法:算法分析方法
  • 经典著作《TAOCP》(《计算机程序设计艺术》)

1.2 大数和数量级的概念

高德纳提出的算法分析方法包含三个核心观点 P32:

  1. 大数定律:比较算法效率时,只需关注数据量趋近无穷大时的情况
  2. 随数据量变化的因素更加重要:固定因素和随数据量变化的因素,后者才是关键
  3. 数据量增大后的量级差距:即使复杂度差异很小,在大数据量时也会导致显著性能差距

大O表示法的核心原则是:当分析算法复杂度时,只考虑N趋近无穷大时与N相关的部分,常数项可以忽略。这个概念帮助我们聚焦算法最本质的效率特征。

葛立恒数:这个超越常规表示法极限的超级大数,展现了数学中数量级的惊人可能性。

1.3 寻找最优算法

最大子序和问题:给定实数序列,找到总和最大的区间。在股票问题上,就是“寻找一只股票的最长有效增长期”。

算法优化过程:

  1. 三重循环的暴力解法,复杂度

  2. 滑动窗口优化,两重循环, 给定起点,一步一步往后滑动到结束点。期间不断加入新的数据。

  3. 分治算法, 递归将序列一分为二,分成 1~K/2 和 K/2+1 ~ K 的两个子序列,找到,比较子序列邻接序列,最终找到最大子序列。

拼接有两种情况:

  • 两个最大子序之间完美拼接,那么直接取从第一个子序的起始位置到第二个子序的结束位置。
  • 如果两个最大子序没有邻接,[p1, q1],[p2, q2],那就情况三选一: [p1, q1], [p2, q2], [p1, q2]

拼接完成之后递归即可。

  1. 正反扫描, - P38 令 S(p,q) 为起始为 p,结束为 q 的序列和。

先正向扫描求和,求序列 S(1,p),找到最大数 Maxf,然后反向扫描求和,求 S(p,n),找到最大数 Maxb,两数之间为最大子序列。 复杂度实际上是 2K,用大O表示成为K。

特殊情况,在 q 某个地方小于零,然后一直小于零。前后项给出的结果无法拼接。

解决方法是将区间在 q 处中断,另起区间进行前后向计算:先固定正的左边界,p,一旦 S(p,q) < 0 则算法在 q 中断,获得区间 [l1, r1],然后继续计算,获得区间 [l2, r2]。

比较两个区间的最大值后获得结果。

  • 示例中区间被拆成了三个部分。

总结:算法优化直觉关键:

  • 起始边界:确定必要的边界扫描
  • 无用功:识别并消除无效操作
  • 逆向思维:尝试逆向思维寻找突破口

1.4 排序算法演进

1.4.1 基础排序算法

选择排序插入排序虽然直观,但都存在 的时间复杂度瓶颈。选择排序的无效比较和插入排序的数组搬移操作都导致了效率低下。

1.4.2 高效排序算法

  1. 归并排序(冯·诺依曼提出): 假设序列 a 和 b 都是已排序序列,每一次归并,将子序列头部的元素一个一个比较,然后放入新序列。属于分治排序策略,需要额外存储空间。

  1. 堆排序(约翰·威廉斯提出): 利用堆做排序,复杂度和归并排序相同,但不占用更多空间

  2. 快速排序(托尼·霍尔发明): 分治概念,挑选枢值放在中间,小的放大的左边。重复这样的操作,最后归并。

算法平均时间复杂度最坏时间复杂度额外空间稳定性
归并排序O(NlogN)O(NlogN)O(N)稳定
堆排序O(NlogN)O(NlogN)O(1)不稳定
快速排序O(NlogN)O(N²)O(logN)不稳定

1.4.3 现代实践

蒂姆排序(结合插入和归并排序)因其优异表现成为Python的默认排序算法。它通过识别数据中的有序区块来提升效率。将每一个递增和递减块堪称整体进行归并排序的算法

1.4.4 排序复杂度下限

通过斯特林公式可以证明,基于比较的排序算法复杂度下限为O(NlogN)。这一理论界限帮助我们评估算法的优化空间。

思考题 1.4 赛跑问题

关于赛跑问题的深入讨论可以参考: 计算之魂思考题1.4——赛跑问题和区间排序