启发式搜索: 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等技术内容,立即学习

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我也要当昏君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值