1. C++爬山算法(Hill Climbing):局部搜索的核心解析
在解决组合优化问题时,我们常常会遇到这样的困境:问题的解空间太大,无法穷举所有可能性。这时候就需要一些高效的搜索策略来帮助我们找到近似最优解。爬山算法就是这样一种简单而有效的局部搜索方法,它像一位登山者一样,在解空间中不断寻找更高的"山峰"。
我第一次接触爬山算法是在解决一个车间调度问题时。当时尝试了各种方法都不理想,直到使用了爬山算法,虽然没能保证全局最优,但在合理时间内得到了可接受的解决方案。这种算法特别适合那些对解质量要求不是极端严格,但需要快速得到结果的场景。
1.1 爬山算法的基本思想
爬山算法的核心思想可以用一个简单的登山类比来解释:假设你被随机放在一座山脉的某个位置,目标是到达最高点。你只能看到周围很近的地方,所以采取的策略就是每次都往看起来更高的方向走,直到周围没有更高的地方为止。
在算法实现中,这对应着以下几个关键步骤:
- 随机生成一个初始解(相当于随机选择一个起始点)
- 评估当前解的质量(计算目标函数值)
- 生成当前解的邻域解(探索周围的点)
- 选择邻域中最优的解作为新的当前解(往更高的方向移动)
- 重复步骤3-4,直到找不到更好的解为止(到达局部顶峰)
注意:爬山算法找到的通常是局部最优解,而非全局最优解。这是由其贪心本质决定的——它只关注眼前的改进,而可能错过全局更好的解。
2. 爬山算法的核心组件与实现
2.1 解表示与目标函数
任何爬山算法的实现都需要首先定义两个关键要素:
- 解的表示:如何用数据结构表示一个解
- 目标函数:如何评估解的质量
以经典的旅行商问题(TSP)为例:
cpp复制// 解表示:城市访问顺序的排列
using Solution = std::vector<int>;
// 目标函数:计算路径总长度
double evaluate(const Solution& sol, const std::vector<std::vector<double>>& dist) {
double total = 0.0;
for (size_t i = 0; i < sol.size(); ++i) {
int from = sol[i];
int to = sol[(i+1)%sol.size()];
total += dist[from][to];
}
return total;
}
2.2 邻域生成策略
邻域生成是爬山算法的核心操作,决定了算法如何探索解空间。常见的邻域生成方式包括:
- 交换邻域:随机交换解中的两个元素
- 插入邻域:将一个元素插入到其他位置
- 反转邻域:反转解中一段连续的元素
cpp复制// 生成交换邻域
Solution generate_swap_neighbor(const Solution& current) {
Solution neighbor = current;
int i = rand() % neighbor.size();
int j = rand() % neighbor.size();
std::swap(neighbor[i], neighbor[j]);
return neighbor;
}
2.3 基本爬山算法实现
有了上述组件,我们可以实现基本的爬山算法:
cpp复制Solution hill_climbing(const std::vector<std::vector<double>>& dist, int max_iter) {
// 初始化随机解
Solution current(dist.size());
std::iota(current.begin(), current.end(), 0);
std::random_shuffle(current.begin(), current.end());
double current_value = evaluate(current, dist);
for (int iter = 0; iter < max_iter; ++iter) {
// 生成邻域解
So
