启发式搜索: A*算法
回顾: 优先队列BFS、最短路
普通BFS:按层扩展
优先队列BFS:每次从队列中取出当前代价最小的状态进行扩展
优先队列BFS的局限性:
一个状态的当前代价最小,只能说明从起始状态到该状态的代价很小,而在未来的搜索中,从该状态到目标状态可能会花费很大的代价。反之亦然。当前代价较大,也许未来代价较小,总代价反而更优。优先队列BFS缺少对未来的预估。
A*算法 – 估价函数
A*算法是一种启发式搜索(Heuristically Search)算法
A*算法的关键是设计一个估价函数:
- 以任意“状态”为输入,计算出从该状态到目标状态所需代价的估计值
- 在搜索中,维护一个堆(优先队列),优先选择“当前代价+未来估价”最小的状态进行扩展
估价函数的设计原则:估值必须比实际更优(估计代价≤未来实际代价)
只要保证上述原则,当目标状态第一次从堆中被取出时,就得到了最优解
为什么?
把好状态估差的后果:
本来在最优解搜索路径上的状态被错误地估计了较大的代价,被压在堆中无法取出,从而导致非最优解搜索路径上的状态不断扩展,直至在目标状态上产生错误的答案
把坏状态估好的后果:
只要估价不大于未来实际代价,这个值总会比最优解更早地被取出,从而得到修正。最坏后果无非就是算的状态多了,跑得慢一些。
否决一个正确idea vs 多看一个垃圾idea
A*算法
A*和优先队列BFS的区别就是:考虑优先级的时候有没有加上未来估价
估价越精准(接近但不超过未来实际代价),A*算法越快
估价等于0,就退化为了优先队列BFS
A*算法的关键:开动脑筋,设计优秀的估价函数(必须要乐观估计,但也要尽量精准)
例如:求第K短路,把当前结点到终点的最短路作为估价函数(最短≤K短)
优先选择“当前走过的路径长度+估价函数”最小的状态扩展
实战
773.滑动谜题
https://leetcode.cn/problems/sliding-puzzle/
class Solution {
public:
int slidingPuzzle(vector<vector<int>>& board) {
string start = zip(board);
string target = zip({{1, 2, 3}, {4, 5, 0}});
//q.push(start);
q.push({-evaluate(start), start});
depth[start] = 0;
while (!q.empty()) {
string s = q.top().second;
q.pop();
int pos = findZeroIndex(s);
if (pos >= 3) expand(s, pos, pos - 3);
if (pos <= 2) expand(s, pos, pos + 3);
if (pos % 3 != 0) expand(s, pos, pos - 1);
if (pos % 3 != 2) expand(s, pos, pos + 1);
if (depth.find(target) != depth.end()) return depth[target];
}
return -1;
}
private:
int evaluate(string &s) {
int now[6];
for (int i = 0; i < 6; i++) {
if (s[i] != '0')
now[s[i] - '0'] = i;
}
int target[6] = {0 ,0 ,1, 2, 3, 4};
int ans = 0;
for (int digit = 1; digit <= 5; digit++) {
int nowx = now[digit] / 3;
int nowy = now[digit] % 3;
int targetx = target[digit] / 3;
int targety = target[digit] % 3;
ans += abs(nowx - targetx) + abs(nowy - targety);
}
return ans;
}
void expand(string& s, int pos, int other) {
string ns = s;
swap(ns[pos] , ns[other]);
if (depth.find(ns) != depth.end()) return;
depth[ns] = depth[s] + 1;
q.push({ - depth[ns] - evaluate(ns), ns});
//q.push(ns);
}
string zip(const vector<vector<int>>& board) {
string res;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
res += '0' + board[i][j];
}
}
return res;
}
int findZeroIndex(string& s) {
for (int i = 0; i < 6; i++) if (s[i] == '0') return i;
return -1;
}
//queue<string> q;
priority_queue<pair<int, string>> q;
unordered_map<string, int> depth;
};
选做题
八数码
https://www.acwing.com/problem/content/847/
https://www.acwing.com/problem/content/181/
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习