算法设计与分析 知识总结

一、算法基础

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

1.1 时间复杂度分析

非递归算法

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

c 复制代码
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²)

再看一个非递归算法:

c 复制代码
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)

递归算法

以阶乘函数为例:

c 复制代码
int fact(int n) {
    if (n == 0 || n == 1)
        return 1;
    else
        return 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)

再以汉诺塔问题为例:

c 复制代码
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

代码实现:

c 复制代码
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) ) 为例:

c 复制代码
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 示例:最大子段和

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

c 复制代码
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 示例:快速排序

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

c 复制代码
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。代码实现:

c 复制代码
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}

代码实现:

c 复制代码
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 ) 降序排序,依次装入。代码实现:

c 复制代码
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],通过广度优先搜索求解。

八、总结

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

相关推荐
绝无仅有39 分钟前
企微审批对接错误与解决方案
后端·算法·架构
用户5040827858392 小时前
1. RAG 权威指南:从本地实现到生产级优化的全面实践
算法
Python×CATIA工业智造3 小时前
详细页智能解析算法:洞悉海量页面数据的核心技术
爬虫·算法·pycharm
无聊的小坏坏4 小时前
力扣 239 题:滑动窗口最大值的两种高效解法
c++·算法·leetcode
黎明smaly4 小时前
【排序】插入排序
c语言·开发语言·数据结构·c++·算法·排序算法
YuTaoShao4 小时前
【LeetCode 热题 100】206. 反转链表——(解法一)值翻转
算法·leetcode·链表
YuTaoShao4 小时前
【LeetCode 热题 100】142. 环形链表 II——快慢指针
java·算法·leetcode·链表
CCF_NOI.4 小时前
(普及−)B3629 吃冰棍——二分/模拟
数据结构·c++·算法
运器1235 小时前
【一起来学AI大模型】支持向量机(SVM):核心算法深度解析
大数据·人工智能·算法·机器学习·支持向量机·ai·ai编程