跳到主要内容经典优化算法解析:分支限界法与动态规划 | 极客日志编程语言算法
经典优化算法解析:分支限界法与动态规划
本文详细介绍了两种经典的优化算法:分支限界法和动态规划。分支限界法通过系统遍历问题空间并结合剪枝策略寻找最优解,适用于组合优化问题,文中提供了 Python 和 C++ 的 0-1 背包问题实现及优化思路。动态规划则利用重叠子问题和最优子结构性质,通过记忆化存储避免重复计算,涵盖自顶向下和自底向上两种模式,并以斐波那契数列和最长公共子序列(LCS)为例展示了基础实现及空间复杂度优化方案。文章对比了两种方法的适用场景,帮助读者掌握算法设计核心思想。
嘘1 浏览 分支限界法概述
分支限界法是一种用于解决组合优化问题的算法策略,主要用于求解 NP 难问题的最优解。该方法通过系统地遍历问题空间,结合剪枝策略避免无效搜索,从而在合理时间内找到最优解。与回溯法不同,分支限界法通常结合优先队列或队列来管理待处理的子问题,优先处理最有希望的节点。
核心思想是将问题分解为多个子问题(分支),并为每个子问题计算界限值(限界),通过比较界限值决定是否继续探索该分支。若某分支的界限值不优于当前已知的最优解,则该分支被剪枝。
算法流程与图文解析
- 初始化:创建优先队列(或队列),将初始问题状态放入队列,并初始化当前最优解。
- 循环处理:从队列中取出一个节点,生成其所有可能的子节点。
- 界限计算:对每个子节点计算界限值。若子节点的界限优于当前最优解,则保留该节点并加入队列;否则剪枝。
- 终止条件:队列为空或满足其他终止条件时结束。

Python 实现框架
import heapq
def branch_and_bound(initial_state):
queue = []
heapq.heappush(queue, initial_state)
best_solution = None
while queue:
current = heapq.heappop(queue)
if is_solution(current):
if best_solution is None or current.cost < best_solution.cost:
best_solution = current
for child in generate_children(current):
if child.bound < best_solution.cost:
heapq.heappush(queue, child)
return best_solution
C++ 实现框架
#include <queue>
#include <vector>
using namespace std;
struct State {
int cost;
bound;
<( State& other) {
bound > other.bound;
}
};
{
priority_queue<State> queue;
queue.(initial);
State best_solution;
best_solution.cost = INT_MAX;
(!queue.()) {
State current = queue.();
queue.();
((current)) {
(current.cost < best_solution.cost) {
best_solution = current;
}
}
(State child : (current)) {
(child.bound < best_solution.cost) {
queue.(child);
}
}
}
best_solution;
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown 转 HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
- HTML 转 Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
int
bool
operator
const
const
return
State branch_and_bound(State initial)
push
while
empty
top
pop
if
is_solution
if
for
generate_children
if
push
return
经典例题:0-1 背包问题
问题描述:给定物品的重量和价值,选择装入背包的物品,使得总重量不超过背包容量且总价值最大。
Python 解法
class Item:
def __init__(self, weight, value):
self.weight = weight
self.value = value
self.ratio = value / weight
def knapsack_branch_and_bound(items, capacity):
items.sort(key=lambda x: -x.ratio)
n = len(items)
best_value = 0
queue = []
queue.append((-0, 0, 0, [False] * n))
while queue:
node = queue.pop(0)
_, current_value, current_weight, taken = node
if current_weight <= capacity and current_value > best_value:
best_value = current_value
if len(taken) == n:
continue
next_idx = taken.index(False)
new_weight = current_weight + items[next_idx].weight
new_value = current_value + items[next_idx].value
new_taken = taken.copy()
new_taken[next_idx] = True
if new_weight <= capacity:
upper_bound = new_value
remaining_capacity = capacity - new_weight
for i in range(next_idx + 1, n):
if remaining_capacity >= items[i].weight:
upper_bound += items[i].value
remaining_capacity -= items[i].weight
else:
upper_bound += remaining_capacity * items[i].ratio
break
if upper_bound > best_value:
queue.append((-upper_bound, new_value, new_weight, new_taken))
upper_bound = current_value
remaining_capacity = capacity - current_weight
for i in range(next_idx + 1, n):
if remaining_capacity >= items[i].weight:
upper_bound += items[i].value
remaining_capacity -= items[i].weight
else:
upper_bound += remaining_capacity * items[i].ratio
break
if upper_bound > best_value:
new_taken[next_idx] = False
queue.append((-upper_bound, current_value, current_weight, new_taken))
return best_value
C++ 解法
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
struct Item {
int weight;
int value;
double ratio;
Item(int w, int v) : weight(w), value(v), ratio((double)v / w) {}
};
struct Node {
int upper_bound;
int value;
int weight;
vector<bool> taken;
bool operator<(const Node& other) const {
return upper_bound < other.upper_bound;
}
};
int knapsack_branch_and_bound(vector<Item>& items, int capacity) {
sort(items.begin(), items.end(), [](Item& a, Item& b) {
return a.ratio > b.ratio;
});
int n = items.size();
int best_value = 0;
priority_queue<Node> queue;
vector<bool> taken(n, false);
queue.push({0, 0, 0, taken});
while (!queue.empty()) {
Node node = queue.top();
queue.pop();
if (node.weight <= capacity && node.value > best_value) {
best_value = node.value;
}
if (node.taken.size() == n) {
continue;
}
int next_idx = distance(node.taken.begin(), find(node.taken.begin(), node.taken.end(), false));
int new_weight = node.weight + items[next_idx].weight;
int new_value = node.value + items[next_idx].value;
vector<bool> new_taken = node.taken;
new_taken[next_idx] = true;
if (new_weight <= capacity) {
int upper_bound = new_value;
int remaining_capacity = capacity - new_weight;
for (int i = next_idx + 1; i < n; ++i) {
if (remaining_capacity >= items[i].weight) {
upper_bound += items[i].value;
remaining_capacity -= items[i].weight;
} else {
upper_bound += remaining_capacity * items[i].ratio;
break;
}
}
if (upper_bound > best_value) {
queue.push({upper_bound, new_value, new_weight, new_taken});
}
}
int upper_bound = node.value;
int remaining_capacity = capacity - node.weight;
for (int i = next_idx + 1; i < n; ++i) {
if (remaining_capacity >= items[i].weight) {
upper_bound += items[i].value;
remaining_capacity -= items[i].weight;
} else {
upper_bound += remaining_capacity * items[i].ratio;
break;
}
}
if (upper_bound > best_value) {
new_taken[next_idx] = false;
queue.push({upper_bound, node.value, node.weight, new_taken});
}
}
return best_value;
}
代码解释
- Python 实现:
Item 类存储物品属性和价值重量比。
- 主函数通过优先队列管理状态节点,界限值计算采用贪心策略估算剩余容量可能的最大价值。
- 剪枝条件为当前节点的界限值不大于已知最优解。
- C++ 实现:
- 使用
priority_queue 实现最大堆,节点按界限值排序。
- 界限计算逻辑与 Python 一致,通过遍历剩余物品估算上限。
- 剪枝逻辑通过比较界限值与当前最优解实现。
性能优化与扩展
- 优先队列选择:根据问题特性选择最大堆或最小堆,确保优先处理最有潜力的分支。
- 界限函数优化:设计更紧密的界限函数(如线性松弛)可显著减少搜索空间。
- 并行化:独立分支可并行处理,适合分布式计算框架。
动态规划概述
动态规划(Dynamic Programming, DP)是一种解决复杂问题的优化技术,通过将问题分解为子问题并存储子问题的解来避免重复计算。动态规划适用于具有重叠子问题和最优子结构性质的问题。动态规划的核心思想是记忆化存储和状态转移。
动态规划分为自顶向下(Top-Down)和自底向上(Bottom-Up)两种方法。自顶向下通常使用递归加记忆化,而自底向上则通过迭代填充表格实现。
动态规划的基本步骤
- 定义状态:确定问题的状态表示方式,通常用一个或多个变量描述问题的子问题。
- 状态转移方程:建立状态之间的关系,明确如何从一个状态转移到另一个状态。
- 初始化:设置初始状态的值,通常是问题的最小规模情况。
- 计算顺序:确定状态的填充顺序,确保在计算当前状态时所需的子状态已经计算完毕。
- 结果提取:从最终状态或表格中提取问题的解。
动态规划的示例图解
以斐波那契数列为例,动态规划可以通过表格存储中间结果:
F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2)
Python 实现动态规划
def fibonacci(n):
if n == 0:
return 0
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
print(fibonacci(5))
C++ 实现动态规划
#include <iostream>
#include <vector>
int fibonacci(int n) {
if (n == 0) return 0;
std::vector<int> dp(n + 1, 0);
dp[1] = 1;
for (int i = 2; i <= n; ++i) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
int main() {
std::cout << fibonacci(5) << std::endl;
return 0;
}
经典例题:最长公共子序列(LCS)
最长公共子序列(Longest Common Subsequence, LCS)是动态规划的经典问题。给定两个字符串,找到它们的最长公共子序列的长度。
问题分析
- 状态定义:设
dp[i][j] 表示字符串 A 的前 i 个字符和字符串 B 的前 j 个字符的 LCS 长度。
- 状态转移方程:
- 如果
A[i-1] == B[j-1],则 dp[i][j] = dp[i-1][j-1] + 1。
- 否则,
dp[i][j] = max(dp[i-1][j], dp[i][j-1])。
- 初始化:
dp[0][j] = 0 和 dp[i][0] = 0,表示空字符串的 LCS 长度为 0。
Python 实现
def lcs(A, B):
m, n = len(A), len(B)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if A[i - 1] == B[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[m][n]
A = "ABCBDAB"
B = "BDCABA"
print(lcs(A, B))
C++ 实现
#include <iostream>
#include <vector>
#include <algorithm>
int lcs(const std::string& A, const std::string& B) {
int m = A.size(), n = B.size();
std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (A[i - 1] == B[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
int main() {
std::string A = "ABCBDAB";
std::string B = "BDCABA";
std::cout << lcs(A, B) << std::endl;
return 0;
}
动态规划的优化
动态规划的空间复杂度可以通过滚动数组或状态压缩进一步优化。例如,LCS 问题可以通过只保留前一行的数据将空间复杂度从 O(mn) 降低到 O(n)。
Python 优化实现
def lcs_optimized(A, B):
m, n = len(A), len(B)
dp = [0] * (n + 1)
for i in range(1, m + 1):
prev = 0
for j in range(1, n + 1):
temp = dp[j]
if A[i - 1] == B[j - 1]:
dp[j] = prev + 1
else:
dp[j] = max(dp[j], dp[j - 1])
prev = temp
return dp[n]
print(lcs_optimized(A, B))
C++ 优化实现
int lcs_optimized(const std::string& A, const std::string& B) {
int m = A.size(), n = B.size();
std::vector<int> dp(n + 1, 0);
for (int i = 1; i <= m; ++i) {
int prev = 0;
for (int j = 1; j <= n; ++j) {
int temp = dp[j];
if (A[i - 1] == B[j - 1]) {
dp[j] = prev + 1;
} else {
dp[j] = std::max(dp[j], dp[j - 1]);
}
prev = temp;
}
}
return dp[n];
}
动态规划的应用场景
- 字符串处理(编辑距离、最长回文子串)
- 图论(最短路径、背包问题)
- 游戏理论(博弈问题)
- 生物信息学(序列对齐)
总结
动态规划是一种强大的算法设计技术,通过分解问题和存储中间结果显著提高效率。掌握动态规划的关键在于理解状态定义和状态转移方程,并通过实践逐步熟悉常见问题的解法。