当前位置: 首页 > news >正文

算法设计与分析 知识总结

一、算法基础

算法是对特定问题求解步骤的描述,是指令的有限序列,具有输入、输出、有穷性、确定性和可行性五个性质。程序则是算法用某种编程语言的具体实现。优秀的算法应具备正确性、健壮性、可理解性、抽象分级和高效性,其中时间复杂度是衡量算法效率的重要标准。常用的时间复杂度符号包括 O(上界)、Ω(下界)和 Θ(紧确界)。

1.1 时间复杂度分析

非递归算法

以嵌套循环为例,分析以下代码的时间复杂度:

for (i = 1; i <= n; ++i)for (j = 1; j <= i - 1; ++j)++x;

外层循环执行 n 次,内层循环对每个 i 执行 i-1 次,总执行次数为:
[
\sum_{i=1}^n (i-1) = 0 + 1 + 2 + \dots + (n-1) = \frac{n(n-1)}{2}
]
因此,时间复杂度为 O(n²)

再看一个非递归算法:

i = 1;
while (i <= n)i = i * 3;

每次循环 i 乘以 3,设循环执行 k 次,则 ( 3^k \leq n < 3^{k+1} )。取对数得:
[
k \leq \log_3 n < k+1
]
因此,循环次数为 ( \lfloor \log_3 n \rfloor + 1 ),时间复杂度为 O(log n)

递归算法

以阶乘函数为例:

int fact(int n) {if (n == 0 || n == 1)return 1;elsereturn n * fact(n - 1);
}

递归方程为:
[
T(n) = T(n-1) + O(1)
]
迭代展开:
[
T(n) = T(n-1) + O(1) = T(n-2) + O(1) + O(1) = \dots = T(0) + n \cdot O(1)
]
因此,时间复杂度为 O(n)

再以汉诺塔问题为例:

void Hanoi(int n, char A, char B, char C) {if (n == 1)printf("A->C");else {Hanoi(n-1, A, C, B);printf("A->C");Hanoi(n-1, B, A, C);}
}

递归方程为:
[
T(n) = 2T(n-1) + O(1)
]
解得:
[
T(n) = 2T(n-1) + 1 = 2[2T(n-2) + 1] + 1 = \dots = 2^n T(0) + 2^{n-1} + \dots + 1 = 2^n - 1
]
时间复杂度为 O(2^n)

二、蛮力法

2.1 设计思想

蛮力法通过遍历所有可能解,采用一定策略逐一处理问题元素,直至找到解。其设计步骤包括:确定枚举范围和约束条件。蛮力法具有通用性、启发性、实用性和准绳性,但效率较低,常用于简单问题或作为基准算法。

2.2 示例:百钱买百鸡

问题:用 100 元买 100 只鸡,公鸡 5 元/只,母鸡 3 元/只,小鸡 3 只 1 元,求解方案。

设公鸡、母鸡、小鸡分别为 a、b、c,则:
[
a + b + c = 100
]
[
5a + 3b + \frac{c}{3} = 100
]
[
c \mod 3 = 0
]
代码实现:

void Chicken_problem(int &k, int g[], int m[], int s[]) {int a, b, c;k = 0;for (a = 0; a <= 20; a++) {for (b = 0; b <= 33; b++) {c = 100 - a - b;if (c % 3 == 0 && 5 * a + 3 * b + c / 3 == 100) {g[k] = a;m[k] = b;s[k] = c;k++;}}}
}

时间复杂度为 O(n²),其中 n 为枚举范围的上限。

2.3 示例:0/1 背包问题

问题:给定 n 种物品,物品 i 的重量为 ( w_i ),价值为 ( v_i ),背包容量为 C,求最大价值组合。

以 n=4,重量 ( {7, 3, 4, 5} ),价值 ( {42, 12, 40, 25} ),C=10 为例,蛮力法枚举所有子集,检查总重量是否不超过 C,记录最大价值。时间复杂度为 O(2^n)

2.4 示例:最大子段和

问题:给定序列 ( (a_1, a_2, \dots, a_n) ),求 ( \sum_{k=i}^j a_k ) 的最大值,若全为负数则返回 0。

以序列 ( (-20, 11, -4, 13, -5, -2) ) 为例:

int MaxSum(int a[], int n) {int sum = a[0], thissum, i, j;for (i = 0; i < n; i++) {thissum = 0;for (j = i; j < n; j++) {thissum += a[j];if (thissum > sum)sum = thissum;}}return sum;
}

时间复杂度为 O(n²)

三、分治法

3.1 设计思想

分治法将规模为 n 的问题分解为 k 个独立子问题,递归求解子问题后合并解。其步骤包括:划分、求解子问题、合并。

3.2 示例:最大子段和

分治法将序列分为两半,最大子段和可能出现在左半部分、右半部分或跨中点。代码实现:

int MaxSum(int a[], int left, int right) {if (left == right) return a[left];int center = (left + right) / 2;int leftSum = MaxSum(a, left, center);int rightSum = MaxSum(a, center + 1, right);int s1 = 0, lefts = 0, s2 = 0, rights = 0, i, j;for (i = center; i >= left; i--) {lefts += a[i];if (lefts > s1) s1 = lefts;}for (j = center + 1; j <= right; j++) {rights += a[j];if (rights > s2) s2 = rights;}int midSum = s1 + s2;return max(max(leftSum, rightSum), midSum);
}

递归方程:
[
T(n) = 2T(n/2) + O(n)
]
时间复杂度为 O(n \log n)

3.3 示例:快速排序

快速排序通过选择枢轴将序列分为两部分,递归排序。代码实现:

int Partition(int r[], int left, int right) {int i = left, j = right, temp = r[i];while (i < j) {while (i < j && temp <= r[j]) j--;if (i < j) { r[i] = r[j]; i++; }while (i < j && temp >= r[i]) i++;if (i < j) { r[j] = r[i]; j--; }}r[i] = temp;return i;
}
void QuickSort(int r[], int left, int right) {if (left < right) {int pivot = Partition(r, left, right);QuickSort(r, left, pivot - 1);QuickSort(r, pivot + 1, right);}
}

以序列 ( {17, 18, 60, 40, 7, 32, 73, 65, 85} ) 为例,排序过程如下:

  1. 第一趟:( {7, 17, 60, 40, 18, 32, 73, 65, 85} )
  2. 第二趟:( {7, 17, 32, 40, 18, 60, 73, 65, 85} )
  3. 第三趟:( {7, 17, 18, 32, 40, 60, 65, 73, 85} )
    平均时间复杂度为 O(n \log n)

四、动态规划法

4.1 设计思想

动态规划通过分解问题为重叠子问题,记录子问题解以避免重复计算。其基本要素是最优子结构和子问题重叠性,求解步骤包括划分子问题、确定动态规划函数、填写表格。

4.2 示例:0/1 背包问题

设 ( V(i, j) ) 为前 i 个物品装入容量 j 的最大价值,递推公式为:
[
V(i, j) = \begin{cases}
V(i-1, j) & \text{if } j < w_i \
\max { V(i-1, j), V(i-1, j-w_i) + v_i } & \text{if } j \geq w_i
\end{cases}
]
以 n=5,重量 ( {3, 2, 1, 4, 5} ),价值 ( {25, 20, 15, 40, 50} ),C=6 为例,动态规划表如下:

V  0  1  2  3  4  5  6
0  0  0  0  0  0  0  0
1  0  0  0 25 25 25 25
2  0  0 20 25 25 45 45
3  0 15 20 35 40 45 60
4  0 15 20 35 40 55 60
5  0 15 20 35 40 55 65

最大价值为 65。代码实现:

int KnapSack(int w[], int v[], int n, int C, int x[]) {int V[n+1][C+1];for (int j = 0; j <= C; j++) V[0][j] = 0;for (int i = 0; i <= n; i++) V[i][0] = 0;for (int i = 1; i <= n; i++)for (int j = 1; j <= C; j++)if (j < w[i-1]) V[i][j] = V[i-1][j];else V[i][j] = max(V[i-1][j], V[i-1][j-w[i-1]] + v[i-1]);for (int i = n, j = C; i > 0; i--)if (V[i][j] > V[i-1][j]) { x[i-1] = 1; j -= w[i-1]; }else x[i-1] = 0;return V[n][C];
}

时间复杂度为 O(n \cdot C)

4.3 示例:最长公共子序列

问题:给定序列 ( X = (a, b, c, b, d, b) ),( Y = (a, c, b, b, a, b, d, b, b) ),求最长公共子序列。

设 ( L(i, j) ) 为 ( X_i ) 和 ( Y_j ) 的最长公共子序列长度,递推公式为:
[
L(i, j) = \begin{cases}
L(i-1, j-1) + 1 & \text{if } x_i = y_j \
\max { L(i, j-1), L(i-1, j) } & \text{otherwise}
\end{cases}
]
代码实现:

int CommonOrder(char x[], int m, char y[], int n, char z[]) {int L[m+1][n+1], S[m+1][n+1];for (int j = 0; j <= n; j++) L[0][j] = 0;for (int i = 0; i <= m; i++) L[i][0] = 0;for (int i = 1; i <= m; i++)for (int j = 1; j <= n; j++)if (x[i-1] == y[j-1]) { L[i][j] = L[i-1][j-1] + 1; S[i][j] = 1; }else if (L[i][j-1] >= L[i-1][j]) { L[i][j] = L[i][j-1]; S[i][j] = 2; }else { L[i][j] = L[i-1][j]; S[i][j] = 3; }int i = m, j = n, k = L[m][n];while (i > 0 && j > 0) {if (S[i][j] == 1) { z[--k] = x[i-1]; i--; j--; }else if (S[i][j] == 2) j--;else i--;}return L[m][n];
}

时间复杂度为 O(m \cdot n)

五、贪心法

5.1 设计思想

贪心法通过局部最优选择逐步逼近全局最优,适用于具有贪心选择性质和最优子结构的问题。

5.2 示例:分数背包问题

问题:给定 n 种物品,重量 ( w_i ),价值 ( v_i ),背包容量 C,物品可部分装入,求最大价值。

策略:按单位重量价值 ( v_i / w_i ) 降序排序,依次装入。代码实现:

int KnapSack(int w[], int v[], int n, int C) {double x[n] = {0}, maxValue = 0;// 假设已按 v[i]/w[i] 降序排序for (int i = 0; i < n && C > 0; i++) {if (w[i] <= C) {x[i] = 1;maxValue += v[i];C -= w[i];} else {x[i] = (double)C / w[i];maxValue += x[i] * v[i];C = 0;}}return maxValue;
}

时间复杂度为 O(n \log n)(排序)。

5.3 示例:Huffman 树

问题:对字符集 ( {A, B, C, D, E, F, G} ) 编码,频率为 ( {9, 11, 5, 7, 8, 2, 3} ),设计最优编码。

构造 Huffman 树:

  1. 初始化:取最小频率 2 和 3,合并为 5。
  2. 重复:合并 5 和 5,得 10;合并 7 和 8,得 15;依次合并直至形成根节点。
    编码方案:
  • A: 00
  • B: 10
  • C: 010
  • D: 110
  • E: 111
  • F: 0110
  • G: 0111
    加权路径长度:
    [
    WPL = (2+3) \cdot 4 + (5+7+8) \cdot 3 + (9+11) \cdot 2 = 120
    ]

六、回溯法

6.1 设计思想

回溯法采用深度优先搜索,通过约束函数和限界函数剪枝,避免无效搜索。

6.2 示例:0/1 背包问题

以 n=3,重量 ( {3, 4, 4} ),价值 ( {15, 12, 8} ),C=9 为例,解空间为完全二叉树,左分支选(1),右分支不选(0)。通过约束(总重量≤C)和限界(上界估算)剪枝,求得最优解。

七、分支限界法

7.1 设计思想

分支限界法采用广度优先搜索,通过限界函数剪枝,常见扩展方式包括队列式和优先队列式。

7.2 示例:任务分配问题

问题:将 n 项任务分配给 n 个人,成本矩阵为:
[
C = \begin{bmatrix}
9 & 2 & 7 & 8 \
6 & 4 & 3 & 7 \
5 & 8 & 1 & 8 \
7 & 6 & 9 & 4
\end{bmatrix}
]
下界为每行最小值之和 ( 2+3+1+4=10 ),上界为贪心解 ( 2+3+5+4=14 )。限界函数为:
[
lb = v + \sum_{k=i+1}^n \min_k
]
通过广度优先搜索,找到最优分配。

7.3 示例:0/1 背包问题

以 n=4,重量 ( {2, 4, 3, 5} ),价值 ( {15, 12, 8, 10} ),C=10 为例,限界函数为:
[
ub = v + (W - w) \cdot \frac{v_{i+1}}{w_{i+1}}
]
目标函数界为 [35, 75],通过广度优先搜索求解。

八、总结

算法设计与分析涉及多种策略,蛮力法适合简单问题,分治法和动态规划适合分解问题,贪心法追求局部最优,回溯法和分支限界法通过剪枝提高效率。选择合适的算法需根据问题特性、时间复杂度和实际需求综合考虑。

http://www.lryc.cn/news/582540.html

相关文章:

  • 【Python-GEE】如何利用Landsat时间序列影像通过调和回归方法提取农作物特征并进行分类
  • Paimon本地表查询引擎LocalTableQuery详解
  • DVWA靶场通关笔记-SQL盲注(SQL Injection Blind Medium级别)
  • 【Mac】实现Docker下载安装【正在逐步完善】
  • hmall学习
  • Apollo源码架构解析---附C++代码设计示例
  • 基于odoo17的设计模式详解---命令模式
  • 如何快速分析光伏电站气象数据?
  • 没合适的组合wheel包,就自行编译flash_attn吧
  • 云原生技术与应用-容器技术技术入门与Docker环境部署
  • 【RL+空战】学习记录01:jsbsim 仿真环境初次学习,F16 战机起飞
  • 吃透二分法的模板解法(适合所有类似于二分的算法题)
  • 【OceanBase 诊断调优】—— SQL 查询触发笛卡尔积怎么处理
  • Proface触摸屏编程软件介绍及下载
  • H3初识——入门介绍之常用中间件
  • vue前置知识-end
  • Vue 整合 Vue Flow:从零构建交互式流程图
  • 理解大模型智能体生态:从 Prompt 到 Agent 的完整信息流解析
  • LeetCode 1248.统计优美子数组
  • 【读代码】GLM-4.1V-Thinking:开源多模态推理模型的创新实践
  • 大模型面试:如何解决幻觉问题
  • 【python】pyserial 在windows 下卡住的bug
  • 在PPT的文本框中,解决一打字,英文双引号就变成中文了
  • 4.权重衰减(weight decay)
  • NumPy-随机数生成详解
  • 初识单例模式
  • 【网络安全】服务间身份认证与授权模式
  • 【Flutter】面试记录
  • Next.js 实战笔记 2.0:深入 App Router 高阶特性与布局解构
  • 算法训练营DAY29 第八章 贪心算法 part02