回顾: 优先队列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等技术内容,立即学习