跳转至
跳转到正文

第 3 章 算法

章节导引

本页从《离散数学讲义》拆分而来,保留原章节锚点,方便从旧总览页和旧链接跳转。

学习目标

把问题描述成算法步骤,并能用渐近记号比较算法规模增长。

前置知识

第 2 章的函数和序列,基础程序流程,以及对循环/递归的直观理解。

建议用时

建议 3–4 小时:算法表达 1 小时,搜索排序 1 小时,复杂度比较 1–2 小时。

练习建议

为搜索或排序写一段伪代码;估算 3 段循环的 Big-O;比较两组函数增长顺序。

参考资料与引用边界

  • 整理者:Lumner。
  • 课程来源:根据 DM/ 目录下的课件整理;本章对应课件:DM3.1-3.3(4).pdf
  • 原始讲义文件:note/离散数学讲义.md
  • 引用边界:这是公开学习笔记,不替代课程正式教材、教师课件或考试要求;外部引用时请注明来自本网站整理版。

3.0 核心目标

算法章节关注三个问题:

  • 如何精确定义一个算法。
  • 如何描述常见算法范式。
  • 如何分析算法效率,尤其是时间复杂度。

3.1 算法

算法定义

算法是用于执行计算或解决问题的一组有限、精确指令。

算法应满足:

性质 含义
输入 从指定集合获得输入值
输出 产生问题所需的输出
确定性 每一步必须清楚明确
正确性 对合法输入产生正确输出
有限性 对任意合法输入都在有限步后停止
有效性 每一步都能实际执行
通用性 能处理一类问题,而非单个样例

伪代码

伪代码介于自然语言和程序语言之间,用于清晰描述算法而不依赖具体语言。

求有限整数序列最大值:

procedure max(a1, a2, ..., an: integers)
    max := a1
    for i := 2 to n
        if max < ai then max := ai
    return max

搜索问题

一般搜索问题:给定列表 \(a_1,a_2,\ldots,a_n\) 和目标 \(x\),找出 \(x\) 在列表中的位置;若不存在则返回 0 或其他失败标记。

线性搜索:

procedure linear_search(x, a1, a2, ..., an)
    i := 1
    while i <= n and x != ai
        i := i + 1
    if i <= n then location := i
    else location := 0
    return location

适用于无序列表,最坏情况下要检查 \(n\) 个元素。

二分搜索适用于递增有序列表:

procedure binary_search(x, a1, a2, ..., an)
    i := 1
    j := n
    while i < j
        m := floor((i + j) / 2)
        if x > am then i := m + 1
        else j := m
    if x = ai then location := i
    else location := 0
    return location

每次将搜索区间约减为约一半,效率远高于线性搜索。

排序问题

排序是把列表元素按升序、字典序等规则排列。排序重要,因为大量计算任务依赖有序数据,例如搜索、数据库索引和数据展示。

课件当前主要提到排序问题本身,具体排序算法可在后续补充。

贪心算法

贪心算法每一步都选择当前看起来最优的局部选择。它不一定总能得到全局最优,但在某些问题上可以证明正确。

找零问题:对于美国硬币面值 25、10、5、1 美分,贪心策略每次选择不超过剩余金额的最大硬币。该策略能得到最少硬币数。

证明贪心正确通常需要:

  1. 找出最优解的结构性质。
  2. 证明存在一个最优解包含贪心第一步选择。
  3. 把问题缩小为子问题。

3.2 函数增长

为什么关心增长率

算法实际运行时间不仅取决于机器速度,也取决于输入规模。增长率描述输入变大时,运行时间如何变化。

例如:

\(30n+8\) 在小规模时可能比 \(n^2+1\) 大,但当 \(n\) 足够大时,二次函数会超过线性函数。

Big-O

\(f,g\) 是从整数或实数到实数的函数。若存在常数 \(C>0\)\(k\),使得当 \(x>k\) 时:

\(|f(x)|\le C|g(x)|\)

则称 \(f(x)\)\(O(g(x))\)

Big-O 给出渐近上界。

例:\(x^2+2x+1\)\(O(x^2)\)

\(x>1\) 时:

\(x^2+2x+1\le x^2+2x^2+x^2=4x^2\)

可取 \(C=4,k=1\)

多项式增长

若:

\(f(x)=a_nx^n+a_{n-1}x^{n-1}+\cdots+a_1x+a_0\)

\(a_n\ne0\),则:

\(f(x)=O(x^n)\)

事实上 \(f(x)=\Theta(x^n)\)

常见增长顺序

从慢到快大致为:

\(1,\ \log n,\ n,\ n\log n,\ n^2,\ n^3,\ 2^n,\ n!\)

重要关系:

  • 任意正幂的对数都比任意正幂的多项式慢。
  • 任意固定次数多项式都比指数函数慢。
  • 指数函数通常比阶乘慢。

Big-Omega 和 Big-Theta

若存在 \(C>0,k\),当 \(x>k\) 时:

\(|f(x)|\ge C|g(x)|\)

\(f(x)=\Omega(g(x))\)。Big-Omega 给出渐近下界。

若:

\(f(x)=O(g(x))\)\(f(x)=\Omega(g(x))\)

则:

\(f(x)=\Theta(g(x))\)

Big-Theta 表示同阶增长。

例:\(3x^2+8x\log x=\Theta(x^2)\),因为 \(x\log x=O(x^2)\),且主导项为 \(x^2\)

组合函数的增长

\(f_1=O(g_1)\)\(f_2=O(g_2)\),则:

\(f_1+f_2=O(\max(g_1,g_2))\)

\(f_1f_2=O(g_1g_2)\)

例:\(n^2+\log(n!)\)

由于 \(\log(n!)=O(n\log n)\),所以整体为 \(O(n^2)\),因为 \(n\log n=O(n^2)\)

3.3 算法复杂度

时间复杂度

时间复杂度估计算法所需基本操作次数随输入规模增长的情况。本课程主要关注时间复杂度。

类型:

  • 最坏情况复杂度:所有输入中最多需要多少操作。
  • 最好情况复杂度:最有利输入下需要多少操作。
  • 平均情况复杂度:按某种输入分布的平均操作次数。

最大值算法复杂度

\(n\) 个数最大值需要比较 \(n-1\) 次,因此时间复杂度为:

\(\Theta(n)\)

线性搜索复杂度

最坏情况:目标在最后一个位置或不存在,需要 \(n\) 次比较,所以为:

\(\Theta(n)\)

若目标一定在列表中且位置等可能,则平均比较次数:

\(\frac{1+2+\cdots+n}{n}=\frac{n+1}{2}\)

仍为 \(\Theta(n)\)

二分搜索复杂度

每次比较后,候选区间约减半。最坏情况下比较次数约为:

\(\lceil\log_2 n\rceil+1\)

所以二分搜索为:

\(\Theta(\log n)\)

可处理、不可处理、不可解

课件提到算法问题的更高层次分类:

  • tractable:通常指多项式时间可解。
  • intractable:可能可解,但没有已知高效算法。
  • unsolvable:不存在算法能解决所有实例。
  • P 与 NP:计算复杂性理论的核心问题。

这些内容当前只作引入,后续若有复杂性理论课件可继续补充。

本章小结与后续扩展

本章已覆盖算法定义、搜索、贪心、渐近记号和复杂度分析。后续可补充:

  • 常见排序算法。
  • 图算法中的 BFS、DFS、最短路。
  • 贪心算法正确性证明模板。
  • P、NP、NP-complete 基础。