
目录
- [C++爬山算法(Hill Climbing):局部搜索(Local Search)的核心解析](#C++爬山算法(Hill Climbing):局部搜索(Local Search)的核心解析)
-
- 引言
- 一、爬山算法核心原理
-
- [1. 核心概念定义](#1. 核心概念定义)
- [2. 爬山算法的核心思想](#2. 爬山算法的核心思想)
- [3. 爬山算法的优缺点](#3. 爬山算法的优缺点)
- 二、爬山算法的常见变体(解决局部最优问题)
- 三、爬山算法通用实现框架(C++)
- 四、C++实战:爬山算法求解TSP问题
-
- [1. 问题背景](#1. 问题背景)
- [2. 完整可运行代码](#2. 完整可运行代码)
- [3. 代码核心解析](#3. 代码核心解析)
- 五、爬山算法的关键优化技巧
-
- [1. 邻域生成优化](#1. 邻域生成优化)
- [2. 初始解优化](#2. 初始解优化)
- [3. 终止条件优化](#3. 终止条件优化)
- [4. 避免局部最优的进阶技巧](#4. 避免局部最优的进阶技巧)
- 六、常见坑点与避坑指南
- 七、总结
C++爬山算法(Hill Climbing):局部搜索(Local Search)的核心解析
引言
爬山算法(Hill Climbing)是最经典的局部搜索算法 之一,它模拟"登山"的过程:从一个初始解出发,不断向"更优"的邻域解移动,直到无法找到更优的解(到达"局部峰值")。作为一种贪心算法,爬山算法实现简单、计算效率高,适合解决组合优化问题(如旅行商问题TSP、数独优化、函数最值求解),但它的核心局限是容易陷入局部最优,无法保证找到全局最优解。本文将从核心原理、实现框架、优化变体到C++实战,帮你彻底掌握爬山算法。
一、爬山算法核心原理
1. 核心概念定义
在讲解算法前,先明确几个关键概念:
- 解空间:问题所有可能解的集合(如TSP的所有路径组合);
- 目标函数:评估解的优劣的函数(如TSP的路径总长度,越小越优);
- 邻域:一个解的"相邻解"集合(如TSP中交换两个城市的位置得到的新路径);
- 局部最优:当前解的所有邻域解都不如它优,但可能不是全局最优;
- 全局最优:解空间中目标函数最优的解。
2. 爬山算法的核心思想
爬山算法的执行流程可总结为5步:
- 初始化:随机生成一个初始解(或指定初始解);
- 评估:计算当前解的目标函数值;
- 生成邻域解:从当前解的邻域中生成所有(或部分)可能的邻域解;
- 选择最优邻域解:找到邻域中目标函数最优的解;
- 判断与移动 :
- 若最优邻域解优于当前解 → 移动到该解,重复步骤2;
- 若没有更优的邻域解 → 停止,当前解为局部最优解。
3. 爬山算法的优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,代码量少 | 易陷入局部最优,无法保证全局最优 |
| 计算效率高,每轮仅需遍历邻域 | 对初始解敏感(不同初始解可能得到不同局部最优) |
| 内存开销小,仅需存储当前解和邻域解 | 可能卡在"平台期"(邻域解与当前解目标函数值相同) |
二、爬山算法的常见变体(解决局部最优问题)
为了缓解"局部最优"的问题,衍生出几种经典变体:
- 随机重启爬山算法:多次随机生成初始解,分别执行爬山算法,最终选择所有局部最优中的最优解;
- 随机爬山算法:不从邻域中选最优解,而是随机选择一个更优的邻域解,增加跳出局部最优的概率;
- 模拟退火算法(进阶):允许一定概率接受更差的解(类似金属退火),随着迭代次数增加,接受差解的概率降低;
- 最陡上升/下降爬山算法:遍历所有邻域解,选择最优的那个(标准爬山算法,也是最常用的)。
三、爬山算法通用实现框架(C++)
以下是标准"最陡下降爬山算法"(目标函数越小越优)的通用框架:
cpp
#include <iostream>
#include <vector>
#include <random>
#include <algorithm>
#include <cmath>
#include <climits>
using namespace std;
// ===================== 核心参数配置 =====================
// 随机数生成器(保证可复现)
random_device rd;
mt19937 rng(rd());
// ===================== 问题相关定义(需根据问题调整) =====================
// 1. 解的表示(示例:TSP问题用城市索引的排列表示路径)
using Solution = vector<int>;
// 2. 目标函数(评估解的优劣,示例:TSP路径总长度)
double calculate_cost(const Solution& sol, const vector<vector<double>>& dist_matrix) {
double total = 0.0;
int n = sol.size();
for (int i = 0; i < n-1; ++i) {
total += dist_matrix[sol[i]][sol[i+1]];
}
// 回到起点(TSP闭环)
total += dist_matrix[sol.back()][sol[0]];
return total;
}
// 3. 生成邻域解(示例:TSP交换两个城市的位置)
vector<Solution> generate_neighbors(const Solution& sol) {
vector<Solution> neighbors;
int n = sol.size();
// 生成所有交换两个城市的邻域解(也可随机生成部分邻域解,提升效率)
for (int i = 0; i < n; ++i) {
for (int j = i+1; j < n; ++j) {
Solution neighbor = sol;
swap(neighbor[i], neighbor[j]);
neighbors.push_back(neighbor);
}
}
return neighbors;
}
// 4. 生成随机初始解(示例:TSP随机排列城市)
Solution generate_random_solution(int n) {
Solution sol(n);
for (int i = 0; i < n; ++i) sol[i] = i;
shuffle(sol.begin(), sol.end(), rng);
return sol;
}
// ===================== 爬山算法核心实现 =====================
// 输入:问题参数(如TSP的距离矩阵)、城市数量
// 输出:局部最优解及其目标函数值
pair<Solution, double> hill_climbing(const vector<vector<double>>& dist_matrix, int n) {
// 步骤1:生成初始解
Solution current_sol = generate_random_solution(n);
double current_cost = calculate_cost(current_sol, dist_matrix);
// 步骤2:迭代寻找更优解
while (true) {
// 步骤3:生成当前解的所有邻域解
vector<Solution> neighbors = generate_neighbors(current_sol);
// 步骤4:找到邻域中的最优解
Solution best_neighbor;
double best_neighbor_cost = INT_MAX;
for (const auto& neighbor : neighbors) {
double cost = calculate_cost(neighbor, dist_matrix);
if (cost < best_neighbor_cost) {
best_neighbor_cost = cost;
best_neighbor = neighbor;
}
}
// 步骤5:判断是否能找到更优解
if (best_neighbor_cost < current_cost) {
// 移动到更优的邻域解
current_sol = best_neighbor;
current_cost = best_neighbor_cost;
} else {
// 无更优解,到达局部峰值,停止
break;
}
}
return {current_sol, current_cost};
}
// ===================== 随机重启爬山算法(优化变体) =====================
pair<Solution, double> random_restart_hill_climbing(const vector<vector<double>>& dist_matrix, int n, int restart_times) {
Solution best_sol;
double best_cost = INT_MAX;
for (int i = 0; i < restart_times; ++i) {
auto [sol, cost] = hill_climbing(dist_matrix, n);
if (cost < best_cost) {
best_cost = cost;
best_sol = sol;
}
cout << "第" << i+1 << "次重启,局部最优解代价:" << cost << endl;
}
return {best_sol, best_cost};
}
四、C++实战:爬山算法求解TSP问题
1. 问题背景
旅行商问题(TSP):给定n个城市及两两之间的距离,求访问所有城市且仅访问一次、最后回到起点的最短路径。
2. 完整可运行代码
cpp
#include <iostream>
#include <vector>
#include <random>
#include <algorithm>
#include <cmath>
#include <climits>
#include <iomanip>
using namespace std;
// 随机数生成器
random_device rd;
mt19937 rng(rd());
// 解的表示:城市索引的排列
using Solution = vector<int>;
// 生成随机距离矩阵(n个城市,距离范围[1, 100])
vector<vector<double>> generate_dist_matrix(int n) {
vector<vector<double>> dist(n, vector<double>(n, 0.0));
uniform_real_distribution<double> distr(1.0, 100.0);
for (int i = 0; i < n; ++i) {
for (int j = i+1; j < n; ++j) {
dist[i][j] = distr(rng);
dist[j][i] = dist[i][j]; // 无向图
}
}
return dist;
}
// 目标函数:计算TSP路径总长度
double calculate_cost(const Solution& sol, const vector<vector<double>>& dist_matrix) {
double total = 0.0;
int n = sol.size();
for (int i = 0; i < n-1; ++i) {
total += dist_matrix[sol[i]][sol[i+1]];
}
total += dist_matrix[sol.back()][sol[0]];
return total;
}
// 生成邻域解:交换两个城市的位置
vector<Solution> generate_neighbors(const Solution& sol) {
vector<Solution> neighbors;
int n = sol.size();
// 优化:随机生成20个邻域解(而非全部,提升效率)
uniform_int_distribution<int> dist_idx(0, n-1);
for (int k = 0; k < 20; ++k) {
int i = dist_idx(rng);
int j = dist_idx(rng);
while (i == j) j = dist_idx(rng);
Solution neighbor = sol;
swap(neighbor[i], neighbor[j]);
neighbors.push_back(neighbor);
}
return neighbors;
}
// 生成随机初始解
Solution generate_random_solution(int n) {
Solution sol(n);
for (int i = 0; i < n; ++i) sol[i] = i;
shuffle(sol.begin(), sol.end(), rng);
return sol;
}
// 标准爬山算法
pair<Solution, double> hill_climbing(const vector<vector<double>>& dist_matrix, int n) {
Solution current_sol = generate_random_solution(n);
double current_cost = calculate_cost(current_sol, dist_matrix);
int iter = 0;
while (true) {
iter++;
vector<Solution> neighbors = generate_neighbors(current_sol);
Solution best_neighbor;
double best_neighbor_cost = INT_MAX;
for (const auto& neighbor : neighbors) {
double cost = calculate_cost(neighbor, dist_matrix);
if (cost < best_neighbor_cost) {
best_neighbor_cost = cost;
best_neighbor = neighbor;
}
}
if (best_neighbor_cost < current_cost) {
current_sol = best_neighbor;
current_cost = best_neighbor_cost;
cout << "迭代" << iter << ":当前最优代价 = " << fixed << setprecision(2) << current_cost << endl;
} else {
cout << "迭代" << iter << ":到达局部峰值,停止" << endl;
break;
}
}
return {current_sol, current_cost};
}
// 随机重启爬山算法
pair<Solution, double> random_restart_hill_climbing(const vector<vector<double>>& dist_matrix, int n, int restart_times) {
Solution best_sol;
double best_cost = INT_MAX;
for (int i = 0; i < restart_times; ++i) {
cout << "\n===== 第" << i+1 << "次重启 =====" << endl;
auto [sol, cost] = hill_climbing(dist_matrix, n);
if (cost < best_cost) {
best_cost = cost;
best_sol = sol;
}
cout << "第" << i+1 << "次重启局部最优代价:" << fixed << setprecision(2) << cost << endl;
}
return {best_sol, best_cost};
}
// 打印解
void print_solution(const Solution& sol, double cost) {
cout << "\n最优路径:";
for (int city : sol) {
cout << city << " → ";
}
cout << sol[0] << endl;
cout << "路径总长度:" << fixed << setprecision(2) << cost << endl;
}
int main() {
// 问题参数:10个城市,重启5次
int n = 10;
int restart_times = 5;
// 生成随机距离矩阵
vector<vector<double>> dist_matrix = generate_dist_matrix(n);
cout << "城市距离矩阵:" << endl;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cout << fixed << setprecision(1) << dist_matrix[i][j] << "\t";
}
cout << endl;
}
// 随机重启爬山算法求解
auto [best_sol, best_cost] = random_restart_hill_climbing(dist_matrix, n, restart_times);
// 输出结果
print_solution(best_sol, best_cost);
return 0;
}
3. 代码核心解析
- 距离矩阵生成:随机生成n个城市的两两距离(无向图);
- 邻域生成优化:随机生成20个邻域解(而非全部),在保证效果的同时提升效率;
- 标准爬山算法:从初始解出发,不断向更优邻域解移动,直到局部峰值;
- 随机重启优化:多次执行爬山算法,选择最优的局部解,缓解局部最优问题;
- 结果输出:打印最优路径和总长度,直观展示求解结果。
五、爬山算法的关键优化技巧
1. 邻域生成优化
- 随机邻域:不生成所有邻域解,而是随机生成部分(如20个),大幅提升效率;
- 邻域策略选择:针对不同问题设计合适的邻域(如TSP除了交换城市,还可反转子路径);
2. 初始解优化
- 若有先验知识,可选择更优的初始解(如TSP的贪心初始解),减少迭代次数;
- 随机重启时,初始解尽量分散在解空间中,避免重复陷入同一局部最优;
3. 终止条件优化
- 除了"无更优邻域解",还可增加迭代次数上限(避免无限循环);
- 若目标函数值长时间无变化(平台期),直接停止;
4. 避免局部最优的进阶技巧
-
随机扰动:当陷入局部最优时,对当前解进行随机扰动(如交换多个城市),跳出局部峰值;
-
模拟退火结合 :允许一定概率接受更差的解,代码只需修改"选择邻域解"的逻辑:
cpp// 模拟退火的接受概率计算(T为温度,随迭代降低) double acceptance_probability(double current_cost, double new_cost, double T) { if (new_cost < current_cost) return 1.0; return exp((current_cost - new_cost) / T); }
六、常见坑点与避坑指南
-
邻域设计不当:
- 坑:邻域解无法覆盖足够的解空间(如TSP仅交换相邻城市),导致过早收敛;
- 避坑:设计多样化的邻域生成策略(交换任意两个城市、反转子路径等);
-
初始解单一:
- 坑:仅用一个初始解,大概率陷入局部最优;
- 避坑:使用随机重启策略,多次生成不同初始解;
-
目标函数计算错误:
- 坑:TSP路径未闭环(忘记加回到起点的距离),导致目标函数值错误;
- 避坑:严格验证目标函数的逻辑,确保与问题需求一致;
-
随机数生成器未初始化:
- 坑:使用未初始化的随机数,导致结果可复现性差;
- 避坑:用
random_device和mt19937初始化随机数生成器;
-
迭代无终止:
- 坑:平台期(邻域解与当前解代价相同)导致无限循环;
- 避坑:增加迭代次数上限,或当连续N轮无改进时停止。
七、总结
核心要点回顾
- 爬山算法核心:贪心策略+局部搜索,从初始解向更优邻域解移动,直到局部峰值;
- 核心步骤:初始化→评估→生成邻域→选择最优邻域→移动/停止;
- 关键局限:易陷入局部最优,可通过随机重启、随机扰动缓解;
- 核心优化 :
- 邻域生成:随机生成部分邻域解,提升效率;
- 初始解:随机重启,选择多个初始解;
- 终止条件:增加迭代上限,避免无限循环;
- 适用场景:组合优化问题(TSP、数独、函数最值),对解的最优性要求不极端,追求效率。
学习建议
- 先实现标准爬山算法求解简单函数最值(如
f(x,y)=x²+y²),理解核心逻辑; - 扩展到TSP问题,掌握邻域设计和目标函数的定义;
- 尝试实现随机重启、模拟退火等变体,对比不同策略的效果;
- 结合实际问题(如车间调度、资源分配),灵活调整邻域和目标函数。
记住:爬山算法的本质是"局部贪心搜索"------它放弃了全局最优的保证,换取了极高的计算效率。在实际工程中,若无需绝对的全局最优,爬山算法是平衡效率和效果的最佳选择之一。