Loading

A*算法

用途

  • 用于最小步数模型,可以在庞大的搜索空间中减少搜索范围;
  • 需要注意的是,如果没有路径可以到达终点,那么A*算法还是会搜索所有的状态.这种情况下,A*算法每选择一个状态的时间复杂度为O(logn),而普通BFS只需要O(1)
  • 其次,A*算法一般应用于非负权边图;如果含负权边且不存在负环,则也可以应用(待定)

原理:

  • A*算法的核心在于估价函数,用于估算当前状态到终点的步数;
  • 假设实际距离为g(state),估计距离为f(state),则估价函数必须满足:f(state) <= g(state)
  • 越接近则算法效果越好,如果估计值全部为0,则退化为dijkstra算法
  • A*算法使用小根堆,按照d(u) + f(u)排序 (假设到达状态u已用步数为d(u))

性质

1.当终点状态第一次出队,即表示找到了最小步数

证明-反证法

  • 假设不是最小步数,即有:d(end) > d(优)
  • 说明堆中尚存在一条最优路径上的点u满足d(优) = d(u) + g(u) >= d(u) + f(u)(堆中至少包含起点)
  • -> d(end) > d(优) >= d(u) + f(u) , 这与小根堆的性质产生矛盾,说明假设错误

2.除终点外,每一点可能被更新多次;

3.A*算法不能保证到每一点(除终点外)在入队/出队时距离是最优的,这一点与dijkstra算法区分

算法步骤

while(q.size())
{
    优先队列中堆顶元素出队;
    
    if(堆顶元素==终点状态) break;

    使用堆顶元素更新其他状态,对更新的状态进行估价,并将其入队
}

例题 -八数码

题目链接

题解

  • 估计函数:计算当前状态下每一数字距离其正确位置的曼哈顿距离之和
  • 八数码问题是否有解:计算对应字符串中逆序对的个数,如果为偶数则有解,反之无解。大概的解释是:每一次变化对于逆序对的影响必定是偶数个,且正解中逆序对的个数也是偶数

代码

#include <iostream>
#include <cstring>
#include <queue>
#include <unordered_map>
#include <algorithm>
using namespace std;
typedef pair<int , string> PIS;
string start;
int dx[] = {-1 , 1 , 0 , 0} , dy[] = {0 , 0 , -1 , 1};

//估价函数
int f(string str)
{
    int res = 0;
    for(int i = 0; i < 9; i ++)
    {
        if(str[i] !='x')
        {
            int t = str[i] - '1';
            res += abs(i/3 - t/3) + abs(i%3 - t%3);
        }
    }
    return res;
}

string bfs()
{
    string over = "12345678x";
    string op = "udlr";
    
    priority_queue<PIS , vector<PIS> , greater<PIS> > heap;
    unordered_map<string , int> dist;
    unordered_map<string , pair<char,string>> prev;
    heap.push({f(start) , start});
    dist[start] = 0;
    
    while(heap.size())
    {
        PIS t = heap.top();
        heap.pop();
        
        string state = t.second;
        if(state == over) break;
        
        int d = dist[state];
        
        int u = state.find('x');
        int a = u / 3 , b = u % 3;
        for(int i = 0; i < 4; i++)
        {
            int x = a + dx[i] , y = b + dy[i];
            if(x < 0 || x >= 3 || y < 0 || y >= 3) continue;
            
            swap(state[u] , state[x*3 + y]);
            if(!dist.count(state) || dist[state] > d + 1)
            {
                dist[state] = d + 1;
                prev[state] = {op[i] , t.second};
                heap.push({f(state) + dist[state] , state});
            }
            swap(state[u] , state[x*3 + y]);
        }
    }   
    
    string res;
    //对状态转移进行回溯,记录操作(udlr)
    while(over != start)
    {
        res += prev[over].first;
        over = prev[over].second;
    }
    
    reverse(res.begin() , res.end());
    return res;
}

int main()
{
    string t;
    for(int i = 0; i < 9; i++)
    {
        char a;  cin >> a;
        start += a;
        if(a != 'x') t += a;
    }
    
    int cnt = 0;
    for(int i = 0; i < 8; i++)
        for(int j  = i + 1; j < 8; j++)
            if(t[i] > t[j]) cnt++;
    
    if(cnt & 1) puts("unsolvable");
    else cout << bfs() << endl;
    
    return 0;
}

例2-第k短路

题目链接

题解

1.终点第几次出队,即表示第几短路,证明与最短路类似

2.估价函数:反向建边,跑一遍dijkstra算法,即可以求出与真实值相同的估计值

代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;

const int N = 1010 , M = 200010;
int e[M] , ne[M] , w[M] , sh[N] , eh[N] , idx;
int dist[N] , cnt[N] , n , m;
bool st[N];
int S , T , K;

void add(int l ,int r , int v , int h[])
{
    e[idx] = r , w[idx] = v , ne[idx] = h[l] , h[l] = idx ++;
}

void dijkstra()
{
    memset(dist , 0x3f , sizeof dist);
    dist[T] = 0;
    priority_queue<PII , vector<PII> , greater<PII> > heap;
    heap.push({0 , T});
    
    while(heap.size())
    {
        PII t = heap.top();
        heap.pop();
        
        int d = t.x , u = t.y;
        if(st[u]) continue;
        
        for(int i = eh[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if(dist[v] > dist[u] + w[i])
            {
                dist[v] = dist[u] + w[i];
                heap.push({dist[v] , v});
            }
        }
        st[u] = true;
    }
}

int astar()
{
    priority_queue<PIII , vector<PIII> , greater<PIII> > heap;
    heap.push({dist[S] , {0 , S}});
    
    while(heap.size())
    {
        PIII t = heap.top();
        heap.pop();
        
        int d = t.y.x , u = t.y.y;
        cnt[u]++;
        if(cnt[T] == K) return d;
        
        for(int i = sh[u]; ~i; i = ne[i])
        {
            int v = e[i];
            
            int tmp = d + w[i];
            if(cnt[v] < K)  //对于在极端情况下可能会有问题,这里是对正确性和时间做了下权衡。
                heap.push({tmp + dist[v] , {tmp , v}});
        }
    }
    
    return -1;
}

int main()
{
    cin >> n >> m;
    
    memset(sh , -1, sizeof sh);
    memset(eh , -1, sizeof eh);
    for(int i = 0; i < m; i++)
    {
        int l , r , v;
        cin >> l >> r >> v;
        add(l ,r , v , sh) , add(r , l , v , eh);
    }
    
    cin >> S >> T >> K;
    if(S == T) K++;  //注意特殊数据:S==T,则 K++剔除自己走到自己的情况
    
    dijkstra();
    
    printf("%d\n" , astar());
    
    return 0;
}

参考资料

Acwing-算法提高课-搜索章节:
https://www.acwing.com/activity/content/introduction/16/

posted @ 2020-11-22 12:05  Krocz  阅读(437)  评论(0编辑  收藏  举报