A*算法
A*算法是在图形平面中,对于有多个节点的路径求出最低通过成本的算法。其属于图遍历算法,算是对BFS算法基础上进行优化改进。其改进是在进行距离估计时,运用了启发式函数进行预估。
具体来说,对于通常的距离计算,假定起点为s,终点为t,从起点到点x的最短真实距离为g(x),x到终点的估计距离为h(x), 那么点x的启发式估计函数为f(x) = g(x) + h(x)。
首先,要运用A*算法有一些前置条件:
1.首先是必须是连通的,如果无解,则说明不连通,那么通过A*算法搜索情况会搜索整个状态空间,这样会比普通BFS更差;
2.对代价估计,就最短路问题而言是最短估计距离,一定要有h(x) <=未来的真实实际代价,这样才能在队列中出队时,第一次遇到目标点时机,此时就是起点到目标点的最短距离。这里需要注意的是,只保证起点到终点的距离最短,并不保证从优先队列中出来的中间点到起点距离最短。
是可以找到反例的。
A*算法本质上是在整个搜索空间上,利用一些hint来遍历一些有效的搜索空间,从而把搜索空间进行压缩。
代码框架:
//
将起点加入到优先队列中;小顶堆
设置起始距离为0;
while (优先队列非空)
{
t <- 优先队列队头出队;
if (t == dst) break;
for (t的所有邻边)
都加入到优先队列中;
}
Astar和Dijkstra算法本质上还是不一样,但形式上还是相似的。
为什么估价函数 <= 真实距离这个条件可以保证出来结果最优?
反证:假设结果不是最优,那么必定有从优先队列中出来的结果dist, 有
dist > dist(最优),而又因为h(x) <=实际真实距离,所以有dist(最优) >= g(x) + h(x)。
那么,有dist > dist(最优) >= g(x) + h(x) = f(x),
而f(x)是每次往队列中存放的值,因此出队元素必定不可能大于f(x),因此矛盾。从而推出结论成立。
下面参考8数码问题:
在一个 3×3 的网格中,1∼8 这 8 个数字和一个 X 恰好不重不漏地分布在这 3×3 的网格中。
例如:
1 2 3
X 4 6
7 5 8
在游戏过程中,可以把 X 与其上、下、左、右四个方向之一的数字交换(如果存在)。
我们的目的是通过交换,使得网格变为如下排列(称为正确排列):
1 2 3
4 5 6
7 8 X
例如,示例中图形就可以通过让 X 先后与右、下、右三个方向的数字交换成功得到正确排列。
交换过程如下:
1 2 3 1 2 3 1 2 3 1 2 3
X 4 6 4 X 6 4 5 6 4 5 6
7 5 8 7 5 8 7 X 8 7 8 X
把 X 与上下左右方向数字交换的行动记录为 u、d、l、r。
现在,给你一个初始网格,请你通过最少的移动次数,得到正确排列。
输入格式
输入占一行,将 3×3 的初始网格描绘出来。
例如,如果初始网格如下所示:
1 2 3
x 4 6
7 5 8
则输入为:1 2 3 x 4 6 7 5 8
输出格式
输出占一行,包含一个字符串,表示得到正确排列的完整行动记录。
如果答案不唯一,输出任意一种合法方案即可。
如果不存在解决方案,则输出 unsolvable。
输入样例:
2 3 4 1 5 x 7 6 8
输出样例
ullddrurdllurdruldr
[解题思路]:
因为将一个数在网格中交换时,可能是有效移动也可能是无效移动,当有效移动,每次移动,至多会缩进与目标状态点的距离,无论如何都会大于等于当前状态与终态各自数字的位置的曼哈顿距离,所以可以以这个曼哈顿距离作为估价函数。
#include <iostream>
#include <cstring>
#include <unordered_map>
#include <queue>
#include <algorithm>
#include <string>
using namespace std;
using PIS = pair<int, string>;
//计算估价函数;
int f(string &s)
{
int ans = 0;
for (int i = 0; i < s.size(); i++)
if (s[i] != 'x') {
int t = s[i] - '1';
//曼哈顿距离;
ans += abs(i/3 - t/3) + abs(i%3 - t%3);
}
return ans;
}
string Astar(string start)
{
string End = "12345678x";
char op[5] = "urdl";
unordered_map<string, int> dist;
priority_queue<PIS, vector<PIS>, greater<PIS>> heap;
unordered_map<string, pair<char, string>> pre; //
dist[start] = 0;
heap.push({f(start), start});
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
while (heap.size()) {
PIS cur = heap.top(); heap.pop();
string s = cur.second;
if (s == End) break;
int x, y;
for (int i = 0; i < s.size(); i++)
if (s[i] == 'x') {
x = i /3, y = i % 3;
break;
}
string src = s;
for (int i = 0; i < 4; i++) {
int x0 = x + dx[i], y0 = y + dy[i];
if (x0 < 0 || x0 >= 3 || y0 < 0 || y0 >= 3) continue;
s = src;
swap(s[x*3 + y], s[x0*3 + y0]);
if (dist.count(s) == 0 || dist[s] > dist[src] + 1) {
dist[s] = dist[src] + 1;
pre[s] = {op[i], src};
//真实距离 + 估计距离;
heap.push({dist[s] + f(s), s});
}
}
}
string ans;
while (End != start) {
ans += pre[End].first;
End = pre[End].second;
}
reverse(ans.begin(), ans.end());
return ans;
}
int main()
{
string start, seq;
for (int i = 0; i < 9; i++) {
char c;
cin >> c;
start += c;
if (c != 'x') seq += c;
}
//逆序对计算;
int ans = 0;
for (int i = 0; i < seq.size(); i++)
for (int j = i + 1; j < seq.size(); j++)
if (seq[i] > seq[j]) ans++;
if (ans % 2) {
cout << "unsolvable" << endl;
} else {
cout << Astar(start) << endl;
}
return 0;
}
参考
1.Acwing提高课