八、算法设计与分析

1 算法设计与分析的基本概念

1.1 算法

  • 定义 :算法是对特定问题求解步骤的一种描述,是有限指令序列,每条指令表示一个或多个操作。
  • 特性 :
    • 有穷性:算法需在有限步骤和时间内结束。
    • 确定性:指令无歧义,相同输入输出唯一。
    • 可行性:操作可通过基本运算有限次实现。
    • 输入:零个或多个输入,来自特定对象集合。
    • 输出:一个或多个输出,与输入有特定关系。

1.2 算法设计

  • 目标 : correctness(正确性)、readability(可读性)、robustness(健壮性)、high efficiency(高效性)。
  • 常用设计技术 : divide and conquer(分治法)、dynamic programming(动态规划法)、greedy algorithm(贪心法)、backtracking(回溯法)、branch and bound(分支限界法)、probabilistic algorithm(概率算法)、approximation algorithm(近似算法)。

1.3 算法分析

  • 定义 :估算算法所需资源,主要有时间复杂度和空间复杂度。
  • 重要性 : 帮助选择最高效的算法。

1.4 算法的表示

  • 自然语言 :易懂但易产生歧义,冗长。
  • 流程图 :直观易懂,但严密性和灵活性不足。
  • 程序设计语言 :可直接执行,但抽象性差,细节繁琐。
  • 伪代码 :结合自然语言和程序设计语言,简洁明了,适合表达算法逻辑,本章采用 C 语言表示算法。

2 算法分析基础

2.1 时间复杂度

时间复杂度分析用于评估算法的运行时间,主要分为三种情况:

  1. 最好情况:算法执行时间最少的情况。
  2. 最坏情况:算法执行时间最多的情况。
  3. 平均情况:算法的平均执行时间,计算公式为 T(n)=i=1mpitiT(n) = \sum_{i=1}^{m} p_i \cdot t_i,其中 pip_i 是输入的概率,tit_i 是输入的执行时间,输入分为 m 类。

2.2 渐进符号

渐进符号用于描述函数的增长速率:

  1. O 记号:表示算法运行时间的上界。
  2. Ω 记号:表示算法运行时间的下界。
  3. Θ 记号:同时表示算法运行时间的上界和下界。

2.3 递归式

递归式用于描述递归算法的时间复杂度。常见的求解方法包括:

  1. 展开法:通过逐层展开递归式,得到一个求和表达式,然后求解。
  2. 代换法:猜测解的形式,然后用数学归纳法证明。
  3. 递归树法:构造递归树,将递归调用的代价累加到递归树的每一层。
  4. 主方法:适用于形式为 T(n) = aT(n/b) + f(n) 的递归式,其中 a ≥ 1b > 1f(n) 是一个递增函数。

3 分治法

3.1 递归的概念

递归是函数直接或间接调用自身的一种方法。递归有两个基本要素:边界条件(决定递归何时终止)和递归模式(如何将问题分解为更小的子问题)。例如,阶乘函数可以用递归定义为:

n!={1if n=0n×(n1)!if n>0n! = \begin{cases} 1 & \text{if } n = 0 \\ n \times (n-1)! & \text{if } n > 0 \end{cases}

对应的 C 代码如下:

1
2
3
4
5
6
int Factorial(int n) {
if (n == 0)
return 1;
else
return n * Factorial(n - 1);
}

3.2 分治法的基本思想

分治法的核心思想是将一个难以直接解决的大问题分解成规模较小的子问题,递归地解决这些子问题,然后将子问题的解合并成原问题的解。分治法在每一层递归上都有三个步骤:

  1. 分解:将原问题分解成多个较小的子问题。
  2. 求解:递归地求解各个子问题。若子问题足够小,则直接求解。
  3. 合并:将子问题的解合并成原问题的解。

3.3 分治法的典型实例

3.3.1 归并排序

归并排序是分治法的经典应用,其基本思想是将待排序元素分成大小大致相同的两个子序列,分别对这两个子序列递归地排序,最后将排好序的子序列合并成一个有序序列。

归并排序的时间复杂度为 O(nlogn)O(n \log n)

3.3.2 最大子段和问题

给定一个由 n 个整数(可能有负整数)组成的序列 a1,a2,,ana_1, a_2, \ldots, a_n,求该序列形如 k=ijak\sum_{k=i}^j a_k 的子段和的最大值。最大子段和问题的分治法策略如下:

  1. 分解:将序列从中间位置分为两段,分别求出左半段和右半段的最大子段和。
  2. 求解:递归地求解左半段和右半段的最大子段和。
  3. 合并:比较左半段、右半段和跨越中间位置的子段和的最大值,取三者中的最大值作为原问题的解。

4 动态规划法

4.1 动态规划法的基本思想

动态规划法与分治法类似,基本思想是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划法求解的问题,经分解得到的子问题往往不是互相独立的,而是具有重叠子问题的性质。因此,动态规划法通过记录已经求解过的子问题的解(通常用表格记录),以避免重复计算,从而提高效率。

动态规划法通常用于求解具有某种最优性质的问题,通过以下步骤实现:

  1. 找出最优解的性质,并刻画其结构特征。
  2. 递归地定义最优值。
  3. 以自底向上的方式计算最优值。
  4. 根据计算最优值时得到的信息,构造一个最优解。

当只需要求出最优值时,步骤(1)-(3)是动态规划算法的基本步骤。若需要构造最优解,则必须执行步骤(4)。

4.2 动态规划法的典型实例

4.2.1 0-1 背包问题

动态规划法通过以下步骤解决 0-1 背包问题:

  1. 刻画 0-1 背包问题的最优解结构
  2. 递归定义最优值
  3. 计算背包问题的最优值
  4. 根据最优解构造最优解

4.2.2 最长公共子序列(LCS)问题

  1. LCS 问题的最优子结构
  2. 递归定义最优值
  3. 计算最优值
  4. 构造最优解

5 贪心法

5.1 贪心法的基本思想

贪心法是一种算法设计技术,常用于解决优化问题。与动态规划法不同,贪心法通过一系列局部最优选择来构建全局最优解。贪心法的特点如下:

  1. 局部最优选择:贪心法在每一步选择中都做出局部最优的选择,而不从全局考虑。这种局部最优选择并不能保证总能得到全局最优解,但在许多情况下可以得到较好的近似解。
  2. 不可回溯性:一旦做出选择,就不会再改变,即使后续发现有更好的选择也不会回溯。

贪心法适用的问题通常具有以下两个重要性质:

  1. 最优子结构:问题的最优解包含其子问题的最优解。
  2. 贪心选择性质:问题的整体最优解可以通过一系列局部最优选择得到。

5.2 贪心法的典型实例

5.2.1 活动选择问题

活动选择问题要求从多个活动集合中选择最大数量的相容活动。贪心算法通过按活动结束时间排序,每次选择最早结束的活动,并跳过与之冲突的活动。

5.2.2 背包问题

背包问题允许将物品部分装入背包,目标是使背包中物品的总价值最大。贪心策略包括:

  1. 按最大价值优先:先放入价值最大的物品。
  2. 按最小重量优先:先放入重量最小的物品。
  3. 按最大单位重量价值优先:先放入单位重量价值最大的物品。

贪心算法的 C 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
float* GreedyKnapsack(int n, int W, int* Weights, float* Values, float* V) {
float* x = (float*)malloc(sizeof(float) * n);
for (int i = 0; i < n; i++)
x[i] = 0;
for (int i = 0; i < n; i++) {
if (Weights[i] <= W) { // 如果背包剩余容量可以装下该物品
x[i] = 1;
W -= Weights[i];
} else
break;
}
if (i < n) { // 如果还有物品可以部分装入背包
x[i] = (float)W / Weights[i];
}
return x;
}

贪心法在上述问题中均能在 O(n)O(n) 时间复杂度内完成,其中 nn 为活动或物品的数量。

6 回溯法

6.1 回溯法的算法框架

6.1.1 问题的解空间

在应用回溯法解问题时,首先需要明确问题的解空间。解空间至少应包含问题的所有(最优)解。

6.1.2 回溯法的基本思想

回溯法从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。在搜索过程中:

  • 若当前扩展结点不能向纵深移动,则成为死结点。
  • 若当前扩展结点成为死结点,则回溯至上一个活结点,继续搜索。

回溯法的算法框架有非递归和递归两种方式。

6.2 回溯法的典型实例

回溯法通过系统地搜索解空间树,并利用限界函数剪枝,适用于解组合数较大的问题,如 0-1 背包问题和 n 后问题。

7 分支限界法

分支限界法类似于回溯法,也是一种在问题的解空间树 T 上搜索问题解的算法。与回溯法不同,分支限界法采用广度优先或最小消耗优先的策略搜索解空间。

分支限界法的搜索方式:

  • 队列式(FIFO)分支限界法:将活结点组织成一个队列,按 FIFO 原则选择下一个扩展结点。
  • 优先队列式分支限界法:将活结点组织成一个优先队列,按优先级选择下一个扩展结点(常用最大堆或最小堆实现)。

8 概率算法

概率算法通过引入随机性选择来优化算法效率,适用于允许较小出错概率的场景。主要类型包括:

  1. 数值概率算法:用于数值问题求解,结果近似且随运行次数增加趋近于真实解。
  2. 蒙特卡罗算法:用于求解问题的精确解,结果可能不正确但可限制出错概率。
  3. 拉斯维加斯算法:结果总是正确,但运行时间不确定,需多次运行以提高效率。
  4. 含舍德算法:通过随机化消除输入实例间计算复杂度差异,保证最坏情况效率。

9 近似算法

近似算法用于在多项式时间内求解 NP 难题,放弃精确解以换取时间效率。设计时需关注:

  1. 时间复杂度:必须为多项式时间。
  2. 解的近似程度:衡量近似解与最优解的差距,常通过近似比表示。

10 数据挖掘算法

10.1 数据挖掘概述

数据挖掘是从大量数据中提取有价值信息和知识的过程,核心是算法,主要任务包括分类、回归、关联规则和聚类等。

10.2 分类

分类是监督学习,根据历史数据预测新数据类型。常见算法:

  • 决策树(ID3、C4.5、CART)
  • 朴素贝叶斯
  • 贝叶斯信念网络
  • 支持向量机(SVM)
  • 神经网络(BP)

10.3 频繁模式和关联规则挖掘

频繁模式挖掘找出数据集中频繁出现的项集,关联规则挖掘基于频繁模式生成规则。常见算法:

  • Apriori
  • FP-growth
  • ECLAT

关联规则衡量指标:

  • 支持度Support(AB)=P(AB)\text{Support}(A \rightarrow B) = P(A \cup B)
  • 置信度Confidence(AB)=P(BA)\text{Confidence}(A \rightarrow B) = P(B|A)

10.4 聚类

聚类是无监督学习,将相似数据对象分为一类。常见方法:

  • 基于划分的方法(K-means、K-medoids)
  • 基于层次的方法(AGNES、DIANA)
  • 基于密度的方法(DBSCAN、OPTICS)
  • 基于网格的方法(STING、CLIQUE)

10.5 数据挖掘的应用

数据挖掘广泛应用于金融、零售、电信等领域,如信贷风险评估、客户细分、交叉销售分析等。

11 智能优化算法

优化技术是一种以数学为基础的应用技术,用于求解各种工程问题的优化。它结合了数学、物理学、生物进化、人工智能等多学科知识,通过模拟或揭示自然现象形成智能优化算法。这些算法具有直观性、并行性和全局搜索能力,适用于复杂问题优化。

人工神经网络(ANN)是一个以有向图结构构成的动态系统,通过输入信号响应来进行信息处理。常见的网络类型包括前馈网络和反馈网络。BP 网络是多层前馈网络的代表,采用误差反向传播算法进行学习。深度神经网络(DNN)通过构建含多层隐藏层的神经网络,模拟人类学习机制,如自动编码器、生成对抗网络等。

遗传算法模仿达尔文的自然选择理论,通过选择、交叉和变异操作进化种群,寻找最优解。其基本步骤包括初始化种群、适应度评估、选择、交叉和变异。遗传算法适用于组合优化、函数优化等问题。

模拟退火法模拟固体退火过程,通过控制温度参数,逐步降低系统能量,最终逼近全局最优解。其主要步骤包括初始化参数、产生新解、接受准则和降温。模拟退火法适用于连续优化和组合优化问题。

禁忌搜索法是一种局部搜索算法,通过记忆机制避免重复搜索,跳出局部最优。其核心是禁忌表,记录已访问解,禁止回访。禁忌搜索法适用于组合优化和调度问题。

蚁群算法模拟蚂蚁觅食行为,通过信息素传递协作寻找最优路径。其主要步骤包括初始化参数、构造解、更新信息素和迭代搜索。蚁群算法适用于路径规划和网络路由问题。

粒子群优化算法模拟鸟群觅食行为,通过个体与群体信息共享优化搜索。其主要步骤包括初始化种群、评估适应度、更新个体和全局最优、调整速度和位置。粒子群优化算法适用于连续优化和组合优化问题。

12 加餐和总结

  • 经过分析发现该问题具有最优子结构和重叠子问题性质。则适宜采用动态规划法算法设计策略得到最优解;
  • 分支限界法类似于回溯法,也是一种在问题的解空间树 T 上搜索问题解的算法。与回溯法不同,分支限界法采用广度优先或最小消耗优先的策略搜索解空间。