软考-软件设计师中级备考 7、算法设计与分析
1、算法的五个特性
- 有穷性:一个算法必须在执行有穷步之后结束,且每一步都在有穷时间内完成。例如,计算 1 到 100 的整数和的算法,通过有限次的加法运算就能得到结果,不会无限循环下去。
- 确定性:算法的每一步骤都必须有确切的定义,对于相同的输入只能得到相同的输出。例如,在一个排序算法中,对于给定的一组数字,按照特定的比较和交换规则进行排序,每次运行该算法,相同的输入序列都会得到相同的排序结果。
- 可行性:算法中的所有操作都可以通过已经实现的基本运算执行有限次来实现。这意味着算法所描述的操作是计算机能够实际执行的,例如加法、减法、乘法、除法等基本运算,以及对数据结构的操作等。
- 输入:一个算法有零个或多个输入,这些输入取自于某个特定的对象集合。例如,在一个求数组中最大值的算法中,数组就是输入,它可以包含任意多个元素,但必须是在一定的数据类型范围内。
- 输出:一个算法有一个或多个输出,这些输出是与输入有特定关系的量。例如,在一个求解方程组的算法中,输入是方程组的系数和常数项,输出则是方程组的解,这些解是通过对输入数据进行一系列运算得到的结果。
2、算法效率的度量
- 时间复杂度:是指算法执行所需的时间与问题规模之间的关系,通常用大 O 表示法来描述。例如,对于一个简单的遍历数组的算法,其时间复杂度为 O (n),其中 n 是数组的长度,表示算法的执行时间与数组长度成正比。常见的时间复杂度有 O (1)(常数时间)、O (log n)(对数时间)、O (n)(线性时间)、O (n log n)(线性对数时间)、O (n²)(平方时间)等,时间复杂度越低,算法效率越高。
- 空间复杂度:是指算法执行过程中所需的存储空间与问题规模之间的关系,同样用大 O 表示法。例如,一个算法只需要使用有限个额外的变量来存储数据,无论输入规模如何,其空间复杂度都是 O (1)。如果算法需要创建一个与输入规模成正比的数组来存储数据,那么其空间复杂度就是 O (n)。在实际应用中,需要根据具体情况平衡时间复杂度和空间复杂度,以选择最合适的算法。
3、算法设计方法
算法设计方法 | 基本思想 | 适用场景 | 经典示例 |
---|---|---|---|
分治法 | 将一个大问题分解为若干个规模较小、相互独立且与原问题形式相同的子问题,递归求解子问题,再合并子问题的解得到原问题的解 | 问题可分解为子问题,子问题解能合并成原问题解,子问题相互独立 | 归并排序、快速排序、二分查找 |
回溯法 | 在解空间树中按深度优先策略,从根节点出发搜索解空间树。当搜索到某一节点时,判断该节点是否包含问题的解,若不包含则跳过该节点为根的子树,向上回溯;若包含则继续向下搜索 | 解空间较大,需要搜索满足约束条件或目标函数的解,如排列组合、子集生成等问题 | 八皇后问题、迷宫求解、图的着色问题、子集和问题 |
贪心法 | 在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最优的算法 | 具有贪心选择性质(整体最优解可通过局部最优选择达到)和最优子结构性质(问题的最优解包含子问题的最优解)的问题 | 活动安排问题、背包问题(部分情况)、哈夫曼编码、最小生成树(Prim 算法、Kruskal 算法) |
动态规划法 | 将问题分解为相互重叠的子问题,通过求解子问题的最优解,并将子问题的解存储起来避免重复计算,从而构造原问题的最优解 | 具有最优子结构性质(问题的最优解包含子问题的最优解)和子问题重叠性质(子问题被重复计算)的问题 | 最长公共子序列问题、矩阵连乘问题、背包问题、最优二叉搜索树构造问题 |
重点:1)分治法经常配合归并排序、快速排序、二分查找一起出题,同时考察算法设计方法和时间复杂度,了解排序和查找算法,因为我有能力直接刷题,所以略过。
2)背包问题涉及到贪心法和动态规划法,必须搞定背包问题
4、背包问题
给定一组物品,每个物品都有自己的重量和价值,同时给定一个背包,其容量有限。要求选择一些物品放入背包中,使得放入物品的总价值最大,同时总重量不能超过背包的容量。
背包问题的分类
- 0-1 背包问题:每个物品只能选择放入背包或不放入背包,即不能选择物品的一部分,每个物品只有 0 和 1 两种状态。
- 完全背包问题:每种物品都有无限件可用,即可以选择一个物品放入背包多次。
- 多重背包问题:每种物品有有限个,即对于每个物品,有一个特定的数量限制。
解决背包问题的方法
- 贪心法
- 算法思想:贪心法是一种基于贪心策略的算法,它在每一步选择中都采取当前状态下的最优选择,即选择价值重量比最大的物品放入背包,直到背包无法再放入任何物品为止。
- 适用场景:适用于某些特殊情况,如物品的价值与重量成正比,或者背包的容量较大,物品数量较少的情况。
- 局限性:贪心法不能保证得到全局最优解。因为它只考虑当前的最优选择,而不考虑整体的最优解。在 0-1 背包问题中,可能会因为选择了一个价值重量比较大但重量也较大的物品,而导致无法放入其他价值更高的物品,从而无法得到最优解。
- 动态规划法
- 算法思想:动态规划法是一种将问题分解为子问题,并通过求解子问题来解决原问题的算法。对于背包问题,通常定义一个二维数组
dp[i][j]
来表示前i
个物品放入容量为j
的背包中所能获得的最大价值。然后通过状态转移方程dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
来求解。其中w[i]
表示第i
个物品的重量,v[i]
表示第i
个物品的价值。 - 适用场景:适用于各种背包问题,包括 0-1 背包问题、完全背包问题和多重背包问题等。能够保证得到全局最优解。
- 实现步骤:首先,初始化
dp
数组,当背包容量为 0 或者没有物品时,最大价值为 0。然后,按照物品的顺序依次考虑每个物品,对于每个物品,从背包容量为 0 开始,逐步增加背包容量,根据状态转移方程计算dp
数组的值。最后,dp[n][C]
即为将n
个物品放入容量为C
的背包中所能获得的最大价值。 - 时间复杂度:时间复杂度为O(nC),其中n是物品的数量,
C
是背包的容量。
- 算法思想:动态规划法是一种将问题分解为子问题,并通过求解子问题来解决原问题的算法。对于背包问题,通常定义一个二维数组
假设有三种物品,分别为物品 A、物品 B、物品 C,它们的重量和价值如下:
物品 | 重量(w) | 价值(v) | 数量(多重背包特有) |
---|---|---|---|
物品 A | 2 | 3 | 1 |
物品 B | 3 | 4 | 无限 |
物品 C | 4 | 5 | 2 |
背包的容量为 8,下面分别针对不同类型的背包问题,使用贪心法和动态规划法进行求解。
0-1 背包问题
每个物品只能选择放或者不放,即每种物品只有一件。
-
贪心法:
- 计算每个物品的价值重量比:物品 A 为 3/2 = 1.5,物品 B 为 4/3 ≈ 1.33,物品 C 为 5/4 = 1.25。
- 按照价值重量比从大到小排序,优先选择物品 A 放入背包,此时背包剩余容量为 8 - 2 = 6。
- 接着考虑物品 B,放入后背包剩余容量为 6 - 3 = 3,无法再放入物品 C。
- 总价值为 3 + 4 = 7。
- 但这种方法不一定能得到最优解,比如如果先选物品 C 和物品 B,总价值为 5 + 4 = 9,大于贪心法得到的结果。
-
动态规划法:
- 定义二维数组
dp[i][j]
表示前i
个物品放入容量为j
的背包中能获得的最大价值。 - 初始化
dp[0][j] = 0
(j
从 0 到 8),dp[i][0] = 0
(i
从 0 到 3)。 - 状态转移方程为
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
(当j >= w[i]
时)。 - 计算过程:
- 对于物品 A(
i = 1
),当j >= 2
时,dp[1][j] = max(dp[0][j], dp[0][j - 2] + 3)
,得到dp[1] = [0, 0, 3, 3, 3, 3, 3, 3, 3]
。 - 对于物品 B(
i = 2
),当j >= 3
时,dp[2][j] = max(dp[1][j], dp[1][j - 3] + 4)
,得到dp[2] = [0, 0, 3, 4, 4, 7, 7, 7, 7]
。 - 对于物品 C(
i = 3
),当j >= 4
时,dp[3][j] = max(dp[2][j], dp[2][j - 4] + 5)
,得到dp[3] = [0, 0, 3, 4, 5, 7, 8, 9, 9]
。
- 对于物品 A(
- 最终
dp[3][8] = 9
为 0-1 背包问题的最优解。
- 定义二维数组
完全背包问题
每种物品都有无限件可供选择。
- 动态规划法:
- 定义二维数组
dp[i][j]
表示前i
种物品放入容量为j
的背包中能获得的最大价值。 - 初始化
dp[0][j] = 0
(j
从 0 到 8),dp[i][0] = 0
(i
从 0 到 3)。 - 状态转移方程为
dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i])
(当j >= w[i]
时),与 0-1 背包的区别在于dp[i][j - w[i]]
而不是dp[i - 1][j - w[i]]
,因为物品可以无限选取。 - 计算过程:
- 对于物品 A(
i = 1
),当j >= 2
时,dp[1][j] = max(dp[0][j], dp[1][j - 2] + 3)
,得到dp[1] = [0, 0, 3, 3, 6, 6, 9, 9, 12]
。 - 对于物品 B(
i = 2
),当j >= 3
时,dp[2][j] = max(dp[1][j], dp[2][j - 3] + 4)
,得到dp[2] = [0, 0, 3, 4, 6, 7, 9, 10, 12]
。 - 对于物品 C(
i = 3
),当j >= 4
时,dp[3][j] = max(dp[2][j], dp[3][j - 4] + 5)
,得到dp[3] = [0, 0, 3, 4, 6, 7, 10, 11, 12]
。
- 对于物品 A(
- 最终
dp[3][8] = 12
为完全背包问题的最优解。
- 定义二维数组
多重背包问题
每种物品有有限的数量。
- 动态规划法(朴素解法):
- 把每种物品的有限数量展开,转化为 0-1 背包问题来处理。物品 A 有 1 件,物品 B 有无限件(这里可以根据背包容量限制实际使用数量),物品 C 有 2 件。
- 重新定义物品数组,相当于有 1 + 8(假设物品 B 最多用 8 个,因为背包容量为 8)+ 2 = 11 个物品的 0-1 背包问题。
- 然后按照 0-1 背包的动态规划方法进行计算,过程较为繁琐,但原理相同。
- 最终可以得到多重背包问题的最优解。
贪心法简单但不一定能得到最优解,动态规划法虽然计算量相对较大,但能够可靠地求出各类背包问题的最优解。