算法复杂度符号
-
算法复杂性的大O记法 (O):大O记法描述了一个算法在最坏情况下的运行时间或空间需求。它关注的是随着输入规模增长,算法所需资源的上界。例如,O(n)表示算法的时间复杂度是线性的,与输入大小成正比。
小Omega记法 (Ω):小Omega记法则是下界的表示,表示算法在所有情况下都必须达到的最低运行时间或空间需求。同样关注的是最坏情况下的基本界限。
大 Theta 记法 (θ):大 Theta 表示一个算法的运行时间和资源需求既在大O下界,也在小Omega上界内,即它是两个界限的交集。这意味着算法在某个范围内是“确切”的,比如 O(n) 和 Ω(n) 代表它的时间复杂度是线性的,且这个线性是确定无疑的。
小omega是下界Ω:大O是上界
备忘录
-
备忘录法 (Memoization):工作原理是存储已经计算过的中间结果,当再次遇到相同的问题时直接返回已有的答案,避免重复计算。常用于动态规划中,如斐波那契数列和汉诺塔问题。应用场景包括递归函数优化,避免无限递归带来的性能问题。
与动态规划算法的自底向上递归方式不同,备忘录方法的递归方式是自顶向下的
如何通过序列X={x1, x2, …, xn}和Y={y1, y2, …, ym}的最长公共子序列Z={z1, z2, …, zk}来证明最长公共子序列问题具有最优子结构性质?
最长公共子序列(Longest Common Subsequence, LCS)问题是一个经典的动态规划问题,其最优子结构性质体现在问题的划分上。当我们试图找出两个序列X和Y的最长公共子序列时,可以通过将原问题分解为更小规模的子问题来找到答案。
最优子结构意味着问题的解可以通过其组成部分的最优解组合得出。对于LCS问题,我们可以通过以下步骤证明这一点:
定义状态:定义一个二维数组DP[i][j],其中DP[i][j]表示X的前i个元素和Y的前j个元素的最长公共子序列的长度。初始时,如果i=0或j=0,LCS为空,即DP[i][j]=0。
填充状态:对于每个位置(i, j),如果Xi = Yj,那么Z的下一个元素是Xi,并且DP[i][j] = DP[i-1][j-1] + 1(因为当前的元素在两个序列中都存在)。如果Xi ≠ Yj,则DP[i][j]取X的前i-1个元素和Y的前j-1个元素的LCS长度的较大者,即DP[i][j] = max(DP[i-1][j], DP[i][j-1])。
递归结构:动态规划的过程实际上是沿着矩阵从左上角向右下角逐步填充的过程。每一个DP[i][j]的值都是基于其左上方的DP[i-1][j-1]和DP[i][j-1]或者DP[i-1][j],这体现了问题的最优子结构。
解的构建:当DP[n][m]被计算出来时,最长公共子序列可以通过回溯得到,从DP[n][m]开始,检查是在行还是列导致了当前的长度,以此逆序添加元素到Z中。
在N皇后问题的回溯算法中,我们使用一个解向量(x_1, x_2, ..., x_N)
来表示一个可能的棋盘布局,其中x_i
代表第i
行皇后的列号。这里,我们深入分析其空间树的表示形式以及回溯过程中需要遵守的约束条件。
N皇后问题是一个经典的回溯算法问题,目标是在N×N的棋盘上放置N个皇后,使得任何两个皇后都不在同一行、同一列或同一对角线上。在这个问题中,我们使用一个解向量
(x_1, x_2, ..., x_N)
来表示一个可能的解,其中x_i
表示第i
个皇后所在的列号(由于皇后肯定在不同的行上,所以行号可以由其位置直接确定)。空间树的表示形式
在回溯算法中,空间树(或称为搜索树)表示了所有可能的解空间。对于N皇后问题,空间树的每个节点都表示一个部分解(即已经放置了一些皇后但尚未完成的状态),而树的叶节点则表示完整的解(即所有N个皇后都已经被成功放置)。
- 根节点:表示没有任何皇后被放置的初始状态。
- 中间节点:表示已经放置了一些皇后但还有皇后未放置的状态。每个中间节点都有多个子节点,分别对应将下一个皇后放置在不同列上的可能性。
- 叶节点:表示N个皇后都已经成功放置的状态,即一个完整的解。
约束条件
在构建空间树时,我们需要应用以下约束条件来剪枝,即避免不必要的搜索:
- 列约束:每个皇后必须位于不同的列上。这是由解向量的定义直接保证的,因为解向量中的每个元素
x_i
都表示第i
个皇后所在的列号。- 行约束:每个皇后必须位于不同的行上。由于我们是按行顺序放置皇后的(即先放第一行的皇后,再放第二行的皇后,以此类推),所以行约束是自动满足的。
- 主对角线约束:任意两个皇后不能位于同一条从左上到右下的主对角线上。这可以通过检查当前要放置的皇后是否与已经放置的皇后在同一条主对角线上来实现。具体地,对于当前要放置在第
i
行、第j
列的皇后,我们需要检查所有已经放置的皇后(即解向量中的前i-1
个元素),看是否存在一个皇后位于第k
行、第l
列(其中k < i
),使得i - k = j - l
(即它们在同一条主对角线上)。- 副对角线约束:任意两个皇后不能位于同一条从左下到右上的副对角线上。这可以通过检查当前要放置的皇后是否与已经放置的皇后在同一条副对角线上来实现。具体地,对于当前要放置在第
i
行、第j
列的皇后,我们需要检查所有已经放置的皇后(即解向量中的前i-1
个元素),看是否存在一个皇后位于第k
行、第l
列(其中k < i
),使得i + k = j + l
(即它们在同一条副对角线上)。在回溯算法中,如果当前的状态违反了上述任何一个约束条件,我们就需要回溯到上一个状态,并尝试其他可能的放置方式。
分析说明分治法与动态规划法的联系与区别。
同:
核心思想:两者都基于将原问题分解为若干个子问题,然后解决这些子问题,最后通过合并子问题的解来得到原问题的解。这种“分而治之”的思想是两者的共同点。
最优子结构:两者都要求原问题具有最优子结构性质,即如果原问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构性质。
异:
- 1.
- 分治法:子问题相互独立
- 动态规划:子问题相互重叠
2.
- 分治法:通常采用递归求解的方式,即不断将问题分解为更小的子问题,直到子问题可以直接求解为止。
- 动态规划法:通常采用迭代法自底向上求解,通过保存已求解过的子问题的解来避免重复计算。在某些情况下,动态规划也可以使用具有记忆功能的递归法自顶向下求解。
给出0-1背包问题的形式化描述。
0-1背包问题是一个经典的组合优化问题,其形式化描述如下:
给定一组物品,每种物品都有自己的重量和价值。在限定的总重量内,如何选择物品放入背包,使得背包内物品的总价值最大。这里的“0-1”表示每种物品只能选择放入背包一次(即放入或不放入),而不能选择放入多次或部分放入。
具体地,假设有
n
个物品和一个容量为W
的背包。第i
个物品的重量为w[i]
,价值为v[i]
。问题的目标是找出一种方案,使得背包内物品的总价值最大。形式化描述如下:
- 物品集合:
items = {1, 2, ..., n}
- 物品
i
的重量:w[i]
- 物品
i
的价值:v[i]
- 背包的容量:
W
目标函数:最大化背包内物品的总价值,即最大化
其中,
x_i
是一个二进制变量,表示物品i
是否被选中放入背包:
- 如果
x_i = 1
,则物品i
被选中放入背包;- 如果
x_i = 0
,则物品i
不被选中。同时,必须满足背包的容量限制:
即,背包内物品的总重量不能超过背包的容量
W
。
说明动态规划算法与贪心算法的联系与区别。
联系:最优子结构加分解子问题
区别:贪心选择策略&重叠子问题
动态规划算法(Dynamic Programming, DP)与贪心算法(Greedy Algorithm)在解决优化问题时都是常用的方法,但它们在原理、应用以及结果上存在一些明显的联系与区别。
联系
- 推导算法:两者都是一种推导算法,即它们都是通过将问题分解成子问题来求解的。
- 最优子结构:两者都需要具有最优子结构,即问题的全局最优解包含其子问题的最优解。
区别
- 子问题解的使用:
- 动态规划:动态规划需要记录之前的所有局部最优解,以便在需要时能够使用它们。
- 贪心算法:贪心算法只关注当前的最优解,而不考虑全局最优解。
- 求解策略:
- 动态规划:通常自底向上求解,从叶子向根,构造子问题的解,最后得到一棵完整的树,并从中选择最优值作为原问题的解。这种方法可能涉及大量的计算和存储。
- 贪心算法:从根出发,每次向下遍历最优子树即可。贪心算法不需要知道一个节点的所有子树情况,因此它通常比动态规划更简单、更直观,但也可能无法得到全局最优解。
- 结果保证与复杂度:
- 动态规划:本质是穷举法,可以保证结果是最佳的。但由于需要保存所有子问题的解,其时间复杂度和空间复杂度可能较高。
- 贪心算法:不能保证求得的最后解是最佳的,但在某些问题上,其解可能接近最优解或满足实际需求。由于不需要保存所有子问题的解,贪心算法的复杂度通常较低。
说明回溯法解决问题的一般步骤。
1.定义解空间
2.确定解空间的组织结构
3.用深度优先搜索的方法搜索,用剪枝函数加快搜索
回溯法(Backtracking)是一种通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯法会通过在上一步进行一些更改来丢弃该解,即“回溯”并尝试其他可能的解。以下是使用回溯法解决问题的一般步骤:
- 定义问题的解空间:
- 首先,需要明确问题的解是什么形式,并确定如何系统地生成这些解。
- 解空间通常是一棵树或图的结构,其中每个节点代表一个可能的部分解。
- 确定搜索策略:
- 选择一种遍历解空间的方法。这通常是通过深度优先搜索(DFS)或广度优先搜索(BFS)来完成的,但回溯法更常使用深度优先搜索。
- 在深度优先搜索中,算法会尽可能深地搜索树的分支。当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。
- 剪枝:
- 剪枝是回溯法中的一个重要概念,用于避免无效和重复的搜索。
- 当确定当前部分解不能导致一个有效解时,可以停止进一步搜索该部分解所在的分支,即“剪去”该分支。
- 剪枝可以基于问题的约束条件、目标函数的性质或其他启发式信息来实现。
- 递归实现:
- 回溯法通常使用递归来实现。递归函数会接收当前部分解和当前搜索位置作为参数。
- 在递归函数中,首先检查当前部分解是否满足问题的约束条件或是否是一个解。如果是,则将其添加到解集中。
- 如果当前部分解不满足约束条件或已经是一个完整解,则回溯到上一个搜索位置并尝试其他可能的解。
- 输出解集:
- 当搜索完整个解空间后,回溯法会输出所有找到的解。
- 如果问题只需要找到一个解,则可以在找到第一个解时立即停止搜索。
- 优化:
- 根据问题的具体性质和需求,可以对回溯法进行优化以提高效率。
- 例如,可以使用启发式信息来指导搜索方向,或者使用数据结构来加速剪枝过程。
- 编写代码:
- 根据上述步骤,编写实现回溯法的代码。
- 在编写代码时,需要注意处理边界条件和异常情况,以确保算法的正确性和健壮性。
通过遵循这些步骤,你可以使用回溯法来解决各种类型的问题,如排列组合问题、图的着色问题、旅行商问题(TSP)等。
0-1背包问题的回溯算法中,其右子树中解的上界是如何确定的?
分析说明分支限界法中活结点表的两种组织形式及其特点。
0-1背包问题的回溯算法中右子树解的上界确定:
在0-1背包问题的回溯算法中,当搜索至解空间树的某一结点时,若选择不放入某个物品(即进入右子树),需要判断继续搜索该右子树是否有意义。为此,我们需要计算当前右子树中解的上界。
具体做法是,将剩余物品按照其单位重量价值(即价值除以重量)进行降序排列,然后依次尝试将这些物品装入背包,直到背包装不下为止。这样,我们就能得到一个在当前情况下可能达到的最大价值,即右子树中解的上界。如果这个上界小于当前已知的最优解(bestp),那么就可以剪去这个右子树,因为继续搜索这个子树不可能得到更优的解。
分支限界法中活结点表的两种组织形式及其特点:
- 队列式分支限界法:
- 组织形式:活结点表被组织成一个队列,按照队列先进先出(FIFO)的原则选取下一个结点作为扩展结点。
- 特点:搜索过程较为简单,按照固定的顺序(通常是按照生成顺序)来扩展结点。但是,这种方法在搜索时不能根据问题的性质来调整搜索的方向,因此在某些情况下可能效率较低。
- 优先队列式分支限界法:
- 组织形式:活结点表被组织成一个优先队列,按照结点的优先级(通常与问题的目标函数相关)来选择下一个扩展结点。
- 特点:能够根据问题的性质来调整搜索的方向,使搜索更加具有针对性。例如,在求解最大价值的问题时,可以优先扩展那些当前价值较高的结点,从而更快地找到最优解。此外,优先队列式分支限界法还常常结合限界函数来剪去那些不可能得到最优解的子树,进一步提高搜索效率。但是,这种方法需要维护一个优先队列,空间开销相对较大。