前言
考前最后一看:一些注意的知识点,同时也是一种考试的题型
问题:最大效益优先为什么是分支界限法的搜索方式?分支界限法是什么?
解:最大效益优先是分支界限法的搜索方式,因为这种方法旨在通过优先考虑可能带来最大效益的分支来快速找到最优解。分支界限法(Branch and Bound)是一种系统地搜索解空间以解决组合优化问题的方法。其主要思想是通过分支(Branching)和界限(Bounding)来减少需要搜索的解空间,从而提高求解效率。
论述题里面也可能会考察一些算法的基本步骤
分支界限法的基本步骤:
-
分支(Branching):
- 将问题分解成多个子问题,形成一个树状结构,每个节点代表一个子问题。 对每个子问题进行进一步的分解,直到达到无法再分解的程度。
-
界限(Bounding):
- 对每个子问题计算一个界限值,这个值通常是子问题的解的上界或下界。如果一个子问题的界限值不可能超过(或达到)当前已知的最优解,则可以剪枝(Pruning),即忽略这个子问题及其后续分支。
-
最大效益优先:
- 在选择下一个要探索的子问题时,优先选择具有最大潜在效益的分支。
- 这可以通过计算每个子问题的效益值,并优先探索具有最高效益值的子问题来实现。
分支界限法的应用:
旅行推销员问题(TSP)、背包问题、整数规划等
如果考你说:最大效益优先是分支界限法的一种搜索策略,这种方法有什么好处,写这样可以更快找到最优解或者逼近最优解
问题:回溯法解旅行商问题,为什么它的解空间是排列树?排列树是什么?
解:回溯法解旅行商问题时,解空间是排列树,因为旅行商问题的解可以表示为一系列城市的排列。排列树是一种用于表示所有可能排列的树状结构。
排列树是什么?
排列树是一种树状结构,用于表示一组元素的所有排列组合。在排列树中:
- 根节点代表起始状态(没有选择任何元素)。
- 每个分支代表在当前排列中添加一个新的元素。
- 每个节点代表一个部分排列(部分解)。
- 叶节点代表完整的排列(一个可能的解)。
举例说明:
假设有三个城市 A、B、C,排列树的结构如下:
( )
/ | \
A B C
/| |\ |\
AB AC BA BC CA CB
/ / / / / /
ABC ACB BAC BCA CAB CBA
在这个排列树中:
- 根节点
()
代表起始状态。 - 第一层的节点
A
、B
、C
代表从根节点出发选择的第一个城市。 - 第二层的节点
AB
、AC
、BA
、BC
、CA
、CB
代表选择了第二个城市后的部分排列。 - 第三层的节点
ABC
、ACB
、BAC
、BCA
、CAB
、CBA
代表所有可能的排列。
每一层增加一个节点加入当前的排列里面
回溯法解旅行商问题的过程(步骤):
- 起始:从根节点开始,表示没有城市被访问。
- 递归构建:每次选择一个未访问的城市,添加到当前路径中。
- 检查解:当路径包含所有城市时,检查是否形成一个完整的回路。
- 回溯:如果当前路径不能形成一个完整的回路,或当前路径已经不可能成为最优解,则回溯到上一个状态,尝试其他分支。
- 剪枝:在构建路径过程中,如果当前部分路径的代价已经超过已知的最优解的代价,则剪枝,避免不必要的计算。
标准的写的话就是
回溯法解旅行商问题时,其解空间是排列树,因为每个可能的旅行路径可以表示为城市的一个排列。排列树是表示所有可能排列的一种树状结构,在回溯算法中,通过遍历排列树中的各个节点,可以系统地搜索所有可能的解,并通过剪枝提高搜索效率。
问题:什么是备忘录法?
解:备忘录法(Memoization)是一种通过存储已经计算过的结果来优化递归算法的方法。它旨在避免重复计算相同的子问题,从而提高算法的效率。备忘录法通常用于解决具有重叠子问题的动态规划问题。
备忘录法的基本思想:
- 递归求解:像通常的递归方法一样解决问题。
- 记录结果:在每次计算出一个子问题的结果后,将其存储在一个数据结构(通常是一个数组或哈希表)中。
- 查找记录:在计算某个子问题时,首先检查该子问题的结果是否已经计算过并存储。如果是,则直接返回存储的结果,而不是重新计算。
- 避免重复计算:通过查找和记录结果,避免了重复计算相同的子问题
举例说明:
假设我们使用备忘录法计算斐波那契数列。斐波那契数列的定义是:
( F(n) = F(n-1) + F(n-2) )
其中 ( F(0) = 0 ),( F(1) = 1 )。
使用备忘录法的代码如下:
def fibonacci(n, memo={}):
# 检查是否已经计算过
if n in memo:
return memo[n]
# 基本情况
if n <= 1:
return n
# 计算并记录结果
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
return memo[n]
print(fibonacci(10)) # 输出55
在这个例子中:
- 递归求解:
fibonacci(n-1, memo) + fibonacci(n-2, memo)
。 - 记录结果:
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
。 - 查找记录:
if n in memo: return memo[n]
。 - 避免重复计算:通过
memo
存储已经计算过的斐波那契数,从而避免了重复计算。
下面这个因为考察到性质,很多老师喜欢考
问题:为什么动态规划通常以自底向上的方式求解最优解?
解:动态规划通常以自底向上的方式求解最优解,因为这种方法能够有效地解决具有重叠子问题和最优子结构性质的问题,通过构建和利用较小子问题的解来逐步解决较大的问题。
动态规划的基本思想(这两个特别要记得):
- 重叠子问题:问题可以分解为多个子问题,并且这些子问题会重复出现。
- 最优子结构:问题的最优解可以通过其子问题的最优解来构建。
自底向上的求解过程:
- 定义状态:确定用于表示子问题解的状态(通常是数组或表格)。
- 确定递推关系:找出状态之间的关系,以便能够通过较小的子问题解来构建较大的子问题解。
- 初始化边界条件:根据问题的具体情况,初始化最小的子问题解。
- 填表求解:按照递推关系,从最小的子问题开始,逐步填充整个状态表,直到求得原问题的解。
举例说明:
以经典的斐波那契数列为例,自底向上的动态规划求解如下:
def fibonacci(n):
if n <= 1:
return n
# 初始化状态表
dp = [0] * (n + 1)
dp[0] = 0
dp[1] = 1
# 自底向上填表
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
print(fibonacci(10)) # 输出55
在这个例子中:
- 定义状态:
dp[i]
表示第i
个斐波那契数。 - 确定递推关系:
dp[i] = dp[i - 1] + dp[i - 2]
。 - 初始化边界条件:
dp[0] = 0
,dp[1] = 1
。 - 填表求解:通过
for
循环,从2
到n
逐步填充状态表,最终得到dp[n]
。
自底向上求解的优势(好处):
- 避免重复计算:每个子问题只计算一次,并存储其结果,避免了重复计算,提高了效率。
- 空间优化:通过适当的状态定义和状态转移,可以进一步优化空间复杂度,例如只保留最近几个状态,而不是完整的状态表。
- 保证最优解:自底向上的求解过程确保每个子问题都被正确计算和利用,从而保证了最终解的最优性。
问题:零一背包问题为什么不能用分治法?
解:零一背包问题不能有效地用分治法来求解,主要原因是分治法通常要求子问题互相独立,而零一背包问题的子问题之间并不独立。以下是详细的解释:
零一背包问题简介:
- 给定一个容量为 ( W ) 的背包和 ( n ) 个物品,每个物品有重量 ( w_i ) 和价值 ( v_i )。
- 目标是找到一个选择方案,使得选中的物品总重量不超过 ( W ),且总价值最大。
分治法的特点:
- 分治法将一个问题分解为多个独立的子问题,分别解决这些子问题,然后合并子问题的解得到原问题的解。
零一背包问题的特性:
-
子问题依赖性:
- 在零一背包问题中,选择某一个物品会影响剩余背包容量,从而影响后续物品的选择。
- 子问题之间存在依赖关系,无法独立求解。例如,如果将问题分解为前一半物品和后一半物品,前一半物品的选择会直接影响后一半物品可用的容量。
-
无法合并子问题解:
- 分治法需要将子问题的解合并成原问题的解。在零一背包问题中,即使能够分别求解子问题,也难以有效地将这些解合并,因为每个子问题的解可能都依赖于其他子问题的选择。
为什么动态规划更适合零一背包问题:
- 动态规划通过将问题分解为规模更小且相互重叠的子问题,并通过存储这些子问题的解来避免重复计算。
- 零一背包问题可以通过动态规划表(或数组)来记录每个子问题的最优解,并在构建更大规模问题的解时利用这些记录。
动态规划求解零一背包问题的例子:
def knapsack(weights, values, W):
n = len(weights)
dp = [[0] * (W + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(W + 1):
if weights[i-1] <= w:
dp[i][w] = max(dp[i-1][w], dp[i-1][w-weights[i-1]] + values[i-1])
else:
dp[i][w] = dp[i-1][w]
return dp[n][W]
weights = [1, 2, 3]
values = [6, 10, 12]
W = 5
print(knapsack(weights, values, W))
这里
dp[i][w]
表示前i
个物品在总重量不超过w
时的最大价值。- 通过迭代计算
dp
表,最终得到最优解。