目录
前言
启发式算法是一类算法,它们使用实用方法在合理的时间内找到复杂问题的解决方案。与确切算法不同,确切算法保证最优解,启发式算法在解决方案质量和计算资源之间进行权衡。
1.什么是启发式算法?
启发式算法是一种基于经验的问题解决技术,它们在合理的时间内找到“足够好”的解决方案。当问题太复杂而无法由确切算法解决时,或者当最优解不是必需的时,它们通常被使用。
这类算法的特点是它们并不保证找到问题的最优解,而是在可接受的时间范围内找到一个近似解。启发式算法通常通过模拟人类的启发式思维或者其他具有启发性的方法来搜索解决方案。这意味着它们在搜索解空间时可能会跳过某些可能的解,或者在搜索过程中采取一些近似的、非确定性的步骤。
在许多实际问题中,寻找确切的最优解可能是不切实际的,因为问题的规模太大或者复杂度太高。在这种情况下,启发式算法提供了一种可行的解决方案,尽管它可能不是最优的,但在很多情况下,这种近似解已经足够满足实际需求。
举例来说,如果我们考虑解决旅行商问题(TSP),寻找全局最优解的确切算法可能需要指数级的时间复杂度。但是,通过使用启发式算法如遗传算法或模拟退火算法,我们可以在相对较短的时间内找到一个接近最优解的解决方案。
2.启发式算法的原理
启发式算法依赖于启发性的方法,这些方法试图捕捉到问题的一些基本特性,并提供相对快速且满意的解决方案。关键在于这里所说的“满意”并不一定总是最佳的,而是一种权衡——在解的质量和获取解的速度之间找到平衡。
启发式算法基于以下原理:
2.1. 贪婪算法
贪婪算法以迭代的方式做出局部最优选择,希望最终达到全局最优。应用案例有赫夫曼编码、Prim和Kruskal最小生成树算法等
#include <stdio.h>
#include <stdbool.h>
#include <limits.h>
#define V 9
int minDistance(int dist[], bool sptSet[]) {
int min = INT_MAX, min_index;
for (int v = 0; v < V; v++) {
if (sptSet[v] == false && dist[v] <= min) {
min = dist[v];
min_index = v;
}
}
return min_index;
}
void printSolution(int dist[]) {
printf("Vertex \t\t Distance from Source\n");
for (int i = 0; i < V; i++) {
printf("%d \t\t %d\n", i, dist[i]);
}
}
void dijkstra(int graph[V][V], int src) {
int dist[V];
bool sptSet[V];
for (int i = 0; i < V; i++) {
dist[i] = INT_MAX;
sptSet[i] = false;
}
dist[src] = 0;
for (int count = 0; count < V-1; count++) {
int u = minDistance(dist, sptSet);
sptSet[u] = true;
for (int v = 0; v < V; v++) {
if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}
printSolution(dist);
}
int main() {
int graph[V][V] = {
{0, 4, 0, 0, 0, 0, 0, 8, 0},
{4, 0, 8, 0, 0, 0, 0, 11, 0},
{0, 8, 0, 7, 0, 4, 0, 0, 2},
{0, 0, 7, 0, 9, 14, 0, 0, 0},
{0, 0, 0, 9, 0, 10, 0, 0, 0},
{0, 0, 4, 14, 10, 0, 2, 0, 0},
{0, 0, 0, 0, 0, 2, 0, 1, 6},
{8, 11, 0, 0, 0, 0, 1, 0, 7},
{0, 0, 2, 0, 0, 0, 6, 7, 0}
};
dijkstra(graph, 0);
return 0;
}
2.2. 局部搜索算法
局部搜索算法提供了一种从问题解空间中的某个点(当前解)出发,通过连续改变当前解来寻找优化解的策略。比如爬山算法和模拟退火算法
山地爬升算法:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 定义问题类
class Problem {
public:
vector<int> initial_state; // 初始状态
vector<vector<int>> expand(vector<int> current) {
// 扩展当前状态的邻居状态
// 此处需要根据具体问题进行实现
}
int value(vector<int> state) {
// 计算状态的价值
// 此处需要根据具体问题进行实现
}
};
// 山地爬升算法
vector<int> hillClimbing(Problem problem) {
vector<int> current = problem.initial_state;
while (true) {
vector<vector<int>> neighbors = problem.expand(current);
if (neighbors.empty()) {
break;
}
random_shuffle(neighbors.begin(), neighbors.end());
vector<int> neighbor = argMax(neighbors); // argMax 函数需要自行实现
if (problem.value(neighbor) <= problem.value(current)) {
break;
}
current = neighbor;
}
return current;
}
int main() {
Problem problem;
// 设置问题的初始状态
// problem.initial_state = ...
// 调用山地爬升算法求解
vector<int> result = hillClimbing(problem);
// 输出结果
cout << "Result: ";
for (int i = 0; i < result.size(); i++) {
cout << result[i] << " ";
}
cout << endl;
return 0;
}
2.3. 遗传算法
遗传算法是模拟自然生物界的遗传和进化过程以求解优化问题的一种全局搜索启发式优化算法。
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
// 计算适应度函数
int calculateFitness(vector<int> genes) {
return accumulate(genes.begin(), genes.end(), 0);
}
// 遗传算法主函数
void geneticAlgorithm() {
vector<int> genes;
for (int i = 0; i < 20; i++) {
genes.push_back(i % 2);
}
int fitness = calculateFitness(genes);
cout << "\n\nGENETIC ALGORITHM:\nGenes: ";
for (int i = 0; i < genes.size(); i++) {
cout << genes[i] << " ";
}
cout << "\nFitness: " << fitness << endl;
}
int main() {
geneticAlgorithm();
return 0;
}
2.4. 模拟退火
模拟退火是一种概率技术,用于近似给定函数的全局最优值。它受到金属加工中退火过程的启发,金属被加热并缓慢冷却以增加其强度。
退火算法:
#include <iostream>
#include <vector>
#include <random>
#include <cmath>
using namespace std;
pair<vector<int>, int> annealing_sol(const vector<int>& fixed, vector<int> keys, double temp, double cooling_rate) {
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<int> uni(2, keys.size() - 1);
while (temp > 1) {
vector<int> new_keys = keys;
int l = uni(gen);
int i = uni(gen);
reverse(new_keys.begin() + i, new_keys.begin() + i + l);
int old_cost = 0;
for (size_t j = 0; j < keys.size() - 1; j++) {
old_cost += fixed[keys[j+1] - keys[j]];
}
int new_cost = 0;
for (size_t j = 0; j < new_keys.size() - 1; j++) {
new_cost += fixed[new_keys[j+1] - new_keys[j]];
}
if (new_cost < old_cost || (double)rand() / RAND_MAX < exp((old_cost - new_cost) / temp)) {
keys = new_keys;
}
temp *= cooling_rate;
}
int total_cost = 0;
for (size_t j = 0; j < keys.size() - 1; j++) {
total_cost += fixed[keys[j+1] - keys[j]];
}
return {keys, total_cost};
}
int main() {
int n = 10000;
vector<int> fixed;
for (int i = 0; i < n * 2 + 1; i++) {
fixed.push_back(rand() % (2 * n + 1) - n);
}
vector<int> keys;
keys.push_back(0);
for (int i = 1; i < n; i++) {
keys.push_back(i);
}
keys.push_back(2 * n);
random_shuffle(keys.begin() + 1, keys.end() - 1);
auto result = annealing_sol(fixed, keys, 10000.0, 0.995);
cout << "Optimal keys: ";
for (auto key : result.first) {
cout << key << " ";
}
cout << "\nTotal cost: " << result.second << endl;
return 0;
}
3.启发式算法的运用
3.1. 优化问题
在优化问题中,启发式算法能够提供快速且常常足够好的解决方案,尤其是在问题的搜索空间过于庞大时。例如,在旅行商问题(TSP)中,我们可能没有足够的资源去计算所有可能的路径以找到绝对最短的路线。此时,启发式算法如遗传算法或蚁群算法能够通过模拟自然界的进化或行为来探索可行的解决方案。
遗传算法伪代码
//伪代码
Initialization();
while (termination condition not met) {
Selection();
Crossover();
Mutation();
Evaluation();
}
3.2. 机器学习
在机器学习中,启发式算法特别是遗传算法,经常被用于优化模型的参数,特别是在模型结构复杂或者标准的梯度下降算法难以应用的场景下。例如,我们可以使用遗传算法调整神经网络中的权重和偏置项,以提高模型的性能。
3.3. 游戏人工智能
在游戏AI中,启发式算法使计算机能够作出快速而高效的决策,甚至在复杂的游戏环境中也是如此。最小最大算法是一个经典的例子,它通过预测对手的最佳反应来作出决策,并进行递归搜索,以找到最优的棋子移动策略。
最小最大算法伪代码
//伪代码
function minimax(node, depth, minimizingPlayer) {
if (depth == 0 || node is a terminal node) {
return the heuristic value of node;
}
if minimizingPlayer {
minValue = +∞;
foreach (child in node) {
value = minimax(child, depth - 1, false);
minValue = min(minValue, value);
}
return minValue;
} else {
maxValue = -∞;
foreach (child in node) {
value = minimax(child, depth - 1, true);
maxValue = max(maxValue, value);
}
return maxValue;
}
}
总结
启发式算法的力量在于其能够以一种智能且高效的方式解决在传统算法中难以企及的复杂问题。这些算法通过采用经验法则来逐步逼近问题的可行解,而不是盲目地探索整个解空间。这种方法在很多实际应用中证明了其价值,从而使得启发式算法成为解决大规模和复杂问题的首选工具之一。