A*
定义
A* 算法是一种在图形或地图中寻找最短路径的启发式搜索算法。它通过综合考虑起始节点到当前节点的实际代价和当前节点到目标节点的预估代价,来决定下一步的搜索方向。
运用情况
- 路径规划:如在地图导航中为车辆、行人规划最优路线。
- 游戏开发:例如在策略游戏中为角色寻找到达目标的最短路径。
- 机器人导航:帮助机器人在复杂环境中找到高效的移动路径。
注意事项
- 启发函数的设计:启发函数的准确性对算法的效率和结果质量有很大影响。如果启发函数过于乐观或悲观,可能导致算法找不到最优解或效率低下。
- 内存使用:在处理大规模的地图或图形时,A* 算法可能需要大量的内存来存储已访问和待访问的节点信息。
- 优化策略:需要根据具体问题选择合适的优化策略,如双向搜索、限制搜索区域等。
解题思路
- 初始化:将起始节点放入开放列表,设置其代价。
- 循环:
- 从开放列表中选择代价最小的节点作为当前节点。
- 将当前节点移出开放列表,放入关闭列表。
- 对当前节点的相邻节点进行处理:
- 如果相邻节点在关闭列表中,忽略。
- 如果不在开放列表中,计算其代价并放入开放列表。
- 如果在开放列表中,更新其代价,如果新代价更小则更新其父节点。
- 直到目标节点被放入关闭列表,或者开放列表为空(未找到路径)。
例如,在地图导航中,起始节点是你的当前位置,目标节点是你的目的地。每个节点代表地图上的一个位置,节点之间的连接表示可能的路径。通过不断评估每个可能的下一步路径的代价,A* 算法能够快速找到从起始点到目标点的最短路径。
A 算法的优缺点*:
优点:
- 高效性:通常能够在合理的时间内找到较优的解,特别是在地图规模不是特别巨大的情况下。
- 最优性保证:在启发函数估计准确的前提下,能够找到最优解。
- 灵活性:可以应用于各种不同类型的图和问题领域,只要能够定义合适的代价函数和启发函数。
- 易于理解和实现:其基本思想相对直观,实现起来并不复杂。
例如,在城市交通导航中,A* 算法能够快速为司机规划出一条相对较短且高效的行驶路线,节省时间和燃油。
缺点:
- 启发函数的依赖:启发函数的质量对算法性能影响极大,如果启发函数设计不好,可能导致算法效率低下或找不到最优解。
- 内存消耗:在处理大规模的问题时,可能需要大量的内存来存储节点信息和搜索过程中的数据。
- 计算开销:计算每个节点的代价和启发值需要一定的计算资源。
- 对复杂环境的适应性有限:在某些极端复杂或动态变化的环境中,可能表现不佳。
比如,在一个实时变化的物流配送网络中,由于路况等因素不断变化,A* 算法可能难以快速适应并重新规划最优路径。
常用的路径规划算法
Dijkstra 算法:
这是一种用于求解图中单个源点到其他所有顶点的最短路径的算法。它会遍历所有可能的路径,逐步更新节点的最短距离,直到找到所有节点的最短路径。优点是能保证找到最短路径,缺点是计算量较大,效率相对较低。
例如,在物流配送中心规划送货路线时,如果只关注从配送中心到各个目的地的最短距离,Dijkstra 算法就可以适用。
Floyd-Warshall 算法:
用于计算图中所有顶点对之间的最短路径。通过动态规划的思想,逐步更新任意两点之间的最短距离。该算法的优点是可以一次性得到所有点对之间的最短路径,缺点是时间和空间复杂度较高。
比如,在分析多个城市之间的最优交通连接时,Floyd-Warshall 算法可以提供全面的路径信息。
蚁群算法:
模拟蚂蚁在寻找食物过程中的行为来寻找最优路径。蚂蚁会根据路径上的信息素浓度选择路径,同时在经过的路径上留下信息素,使得后续的蚂蚁更倾向于选择较优的路径。优点是具有较强的全局搜索能力和自适应性,缺点是计算时间可能较长,参数设置较复杂。
在大规模的网络路由规划中,蚁群算法可以发现复杂网络中的优质路径。
粒子群优化算法:
通过模拟鸟群觅食的行为来寻找最优解。粒子在解空间中移动,根据自身和群体的历史最优位置来更新自己的位置。优点是实现简单,收敛速度较快,缺点是容易陷入局部最优。
例如,在无人机的自主飞行路径规划中,可以使用粒子群优化算法来找到合适的路线。
遗传算法:
基于生物进化的思想,通过选择、交叉和变异等操作来优化解。优点是能够处理复杂的优化问题,具有较好的全局搜索能力,缺点是计算量较大,编码和解码过程可能较复杂。
比如,在机器人在复杂环境中的路径规划问题中,遗传算法可以发挥作用。
AcWing 179. 八数码
题目描述
AcWing 179. 八数码 - AcWing
运行代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <unordered_map>
using namespace std;
typedef pair<int, string> PIS;
int f(string state)
{
int res = 0;
for(int i = 0; i < state.size(); i ++ )
if(state[i] != 'x')
{
int t = state[i] - '1';
res += abs(i / 3 - t / 3) + abs(i % 3 - t % 3);
}
return res;
}
string bfs(string start)
{
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
char op[4] = {'u', 'r', 'd', 'l'};
string end = "12345678x";
unordered_map<string, int> dist;
unordered_map<string, pair<string, char>> prev;
priority_queue<PIS, vector<PIS>, greater<PIS>> heap;
heap.push({f(start), start});
dist[start] = 0;
while(heap.size())
{
auto t = heap.top();
heap.pop();
string state = t.second;
if(state == end) break;
int step = dist[state];
int x, y;
for(int i = 0; i < state.size(); i ++ )
if(state[i] == 'x')
{
x = i / 3, y = i % 3;
break;
}
string scource = state;
for(int i = 0; i < 4; i ++ )
{
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a <= 2 && b >= 0 && b <= 2)
{
swap(state[x * 3 + y], state[a * 3 + b]);
if(!dist.count(state) || dist[state] > step + 1)
{
dist[state] = step + 1;
prev[state] = {scource, op[i]};
heap.push({dist[state] + f(state), state});
}
swap(state[x * 3 + y], state[a * 3 + b]);
}
}
}
string res;
while(end != start)
{
res += prev[end].second;
end = prev[end].first;
}
reverse(res.begin(), res.end());
return res;
}
int main()
{
string g, c, seq;
while(cin >> c)
{
g += c;
if(c != "x") seq += c;
}
int res = 0;
for(int i = 0; i < seq.size(); i ++ )
for(int j = i + 1; j < seq.size(); j ++ )
if(seq[i] > seq[j]) res ++ ;
if(res & 1) puts("unsolvable");
else cout << bfs(g) << endl;
return 0;
}
代码思路
f
函数用于计算一个状态与目标状态的曼哈顿距离估计值,作为启发函数。bfs
函数实现了广度优先搜索来求解八数码问题的最优路径。- 定义了移动方向和对应的操作字符。
- 使用优先队列进行搜索,根据当前状态的步数和启发函数值进行排序。
- 通过遍历四个方向,尝试交换空白格与相邻位置的数字,更新状态和相关信息。
- 最终通过回溯得到从起始状态到目标状态的路径。
main
函数用于读取输入的八数码状态,判断是否可解,并调用bfs
函数求解。
改进思路
- 数据结构优化:考虑使用更高效的数据结构来存储已访问的状态,例如布隆过滤器或者哈希表的优化实现,以提高查找速度。
- 启发函数改进:进一步优化启发函数,使其更准确地估计到目标状态的距离,可能会提高搜索效率。
- 剪枝策略:分析问题的特性,添加更多有效的剪枝条件,避免不必要的搜索。
- 并行化:如果计算资源允许,可以考虑对搜索过程进行并行化处理,加快搜索速度。
- 代码可读性和可维护性:添加更多的注释来解释关键代码段的作用和逻辑。将一些复杂的逻辑提取为独立的函数,以提高代码的可读性和可维护性。
其它代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <unordered_map>
using namespace std;
typedef pair<int, string> PIS;
int f(string state) {
int res = 0;
for (int i = 0; i < state.size(); i++)
if (state[i]!= 'x') {
int t = state[i] - '1';
res += abs(i / 3 - t / 3) + abs(i % 3 - t % 3);
}
return res;
}
string bfs(string start) {
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
char op[4] = {'u', 'r', 'd', 'l'};
string end = "12345678x";
unordered_map<string, int> dist;
unordered_map<string, pair<string, char>> prev;
priority_queue<PIS, vector<PIS>, greater<PIS>> heap;
heap.push({f(start), start});
dist[start] = 0;
while (heap.size()) {
auto t = heap.top();
heap.pop();
string state = t.second;
if (state == end) break;
int step = dist[state];
int x, y;
for (int i = 0; i < state.size(); i++)
if (state[i] == 'x') {
x = i / 3, y = i % 3;
break;
}
string scource = state;
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a <= 2 && b >= 0 && b <= 2) {
swap(state[x * 3 + y], state[a * 3 + b]);
if (!dist.count(state) || dist[state] > step + 1) {
dist[state] = step + 1;
prev[state] = {scource, op[i]};
heap.push({dist[state] + f(state), state});
}
swap(state[x * 3 + y], state[a * 3 + b]);
}
}
}
string res;
while (end!= start) {
res += prev[end].second;
end = prev[end].first;
}
reverse(res.begin(), res.end());
return res;
}
int main() {
string g, c, seq;
while (cin >> c) {
g += c;
if (c!= "x") seq += c;
}
int res = 0;
for (int i = 0; i < seq.size(); i++)
for (int j = i + 1; j < seq.size(); j++)
if (seq[i] > seq[j]) res++;
if (res & 1) puts("unsolvable");
else cout << bfs(g) << endl;
return 0;
}
代码思路
f
函数用于计算当前状态与目标状态的曼哈顿距离估计值。bfs
函数通过广度优先搜索和优先队列来找到从初始状态到目标状态的最优路径。- 定义了移动方向和对应的操作字符。
- 维护了已访问状态的距离和前驱信息。
- 通过交换
x
与相邻位置的数字来探索新状态。
main
函数读取输入的初始状态,判断其是否可解,然后调用bfs
函数求解并输出结果。
改进思路
- 可以考虑使用位运算来表示状态,减少存储空间和计算量。
- 对于启发函数,可以进一步研究更精确的估计方式。
- 对搜索过程中的一些边界情况和异常处理进行更完善的考虑。