搜索专项---A*模型


文章目录

  • 第K短路
  • 八数码

一、第K短路OJ链接

        本题思路:A* 应用场景:起点→终点的最短距离,状态空间 >> 1e10,启发函数减小搜索空间A*算法:while(q.size())t ← 优先队列的队头 (建小根堆的方式进行求解) 当终点第一次出队时 break;从起点到当前点的真实距离 d_real,从当前点到终点的估计距离 d_estimate选择一个估计距离最小的点 min(d_estimate),for j in ne[t]:将邻边入队

A*算法条件:
估计距离<=真实距离d[state] + f[state] = 起点到state的真实距离 + state到终点的估计距离=估计距离                                                              
d[state] + g[state] = 起点到state的真实距离 + state到终点的真实距离=真实距离一定是有解才有 d[i]   >= d[最优] = d[u]+f[u],f[u] >= 0。

证明终点第一次出队列即最优解

    1 假设终点第一次出队列时不是最优
      则说明当前队列中存在点u
         有 d[估计]< d[真实]
      d[u] + f[u] <= d[u] + g[u] = d[队头终点]
      即队列中存在比d[终点]小的值,
    2 但我们维护的是一个小根堆,没有比d[队头终点]小的d[u],矛盾

A* 不用判重
以边权都为1为例
  A o→o→o
    ↑   ↓
  S o→o→o→o→o→o→o T
      B
dist[A] = dist[S]+1 + f[A] = 7
dist[B] = dist[S]+1 + f[B] = 5
则会优先从B这条路走到T
B走到T后再从A这条路走到T

#include 

#define x first
#define y second

using namespace std;

typedef pair PII;
typedef pair PIII;
const int N = 1010, M = 200010;

int n, m, S, T, K;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];

void add(int h[],int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}

void dijkstra()
{
    priority_queue,greater> heap;
    heap.push({0,T});//终点
    memset(dist, 0x3f, sizeof dist);
    dist[T] = 0;

    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.y;
        if(st[ver]) continue;
        st[ver] = true;

        for(int i=rh[ver];i!=-1;i=ne[i])
        {
            int j = e[i];
            if(dist[j]>dist[ver]+w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j],j});
            }
        }
    }
}

int astar()
{
    priority_queue, greater> heap;
    // 谁的d[u]+f[u]更小 谁先出队列
    heap.push({dist[S], {0, S}});
    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int ver = t.y.y,distance = t.y.x;
        cnt[ver]++;
        //如果终点已经被访问过k次了 则此时的ver就是终点T 返回答案

        if(cnt[T]==K) return distance;

        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j = e[i];
            /* 
            如果走到一个中间点都cnt[j]>=K,则说明j已经出队k次了,且astar()并没有return distance,
            说明从j出发找不到第k短路(让终点出队k次),
            即继续让j入队的话依然无解,
            那么就没必要让j继续入队了
            */
            if(cnt[j] < K)
            {
                // 按 真实值+估计值 = d[j]+f[j] = dist[S->t] + w[t->j] + dist[j->T] 堆排
                // 真实值 dist[S->t] = distance+w[i]
                heap.push({distance+w[i]+dist[j],{distance+w[i],j}});
            }
        }
    }
    // 终点没有被访问k次
    return -1;
}

int main()
{
    cin >> m >> n;
    memset(h,-1,sizeof h);
    memset(rh,-1,sizeof rh);
    for(int i=0;i> a >> b >> c;
        add(h,a,b,c);
        add(rh,b,a,c);
    }
    
    //本题可以建反向边的方式
    cin >> S >> T >> K;
    // 起点==终点时 则d[S→S] = 0 这种情况就要舍去 ,总共第K大变为总共第K+1大 
    if (S == T) K ++ ;
    // 从各点到终点的最短路距离 作为估计函数f[u]
    dijkstra();
    cout << astar();
    return 0;
}

二、八数码OJ链接

        本题思路:采用AcWing 179. 对八数码问题的一些补充 - AcWing

#include 

#define x first 
#define y second

int f(std::string state)
{
    int dist=0;
    for(int i=0;i<9;i++)
        if(state[i]!='x'){
            int t=state[i]-'1';//计算下标
            dist=dist+abs(i/3-t/3)+abs(i%3-t%3);//曼哈顿距离
        }
    return dist;
}

std::string bfs(std::string start)
{
    std::string end="12345678x";
    
    std::unordered_map d;
    std::priority_queue,std::vector>,std::greater>> heap;
    std::unordered_map> last;//存储一个元素由哪种状态,经过哪种操作得来,跟前面几题一样

    heap.push({f(start),start});//加入起点
    d[start]=0;//起点到起点的距离为0
    //要将操作数组与坐标变化数组一一对应
    char oper[]="udlr";
    int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};

    while(heap.size())
    {
        auto t=heap.top();//队头
        heap.pop();//弹出
        std::string state=t.y;//记录

        if(t.y==end) break;//终点出列的话就退出

        int x,y;//查找x的横纵坐标
        for(int i=0;i<9;i++)
        if(state[i]=='x')
        {
            x=i/3,y=i%3;
            break;
        }

        std::string init=state;
        for(int i=0;i<4;i++)
        {
            int a=x+dx[i],b=y+dy[i];
            if(a<0||a>=3||b<0||b>=3) continue;//越界就跳过
            std::swap(state[a*3+b],state[x*3+y]);//交换下标位置
            if(!d.count(state)||d[state]>d[init]+1)//如果没有被记录或者小于记录值
            {
                d[state]=d[init]+1;//更新距离
                heap.push({f(state)+d[state],state});//加入堆中
                last[state]={init,oper[i]};//标记由哪种状态转移而来,并且记录执行的操作
            }
            state=init;//因为要扩展到四个方向,所以要还原
        }
    }

    std::string ans;
    //跟前面几题原来相同
    while(end!=start)
    {
        ans+=last[end].y;
        end=last[end].x;
    }
    reverse(ans.begin(),ans.end());//将其反转
    return ans;
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);std::cout.tie(nullptr);
    
    std::string start,x;
    char c;
    
    while(std::cin>>c){//忽略空格
        start+=c;
        if(c!='x') x+=c;
    }
    
    //八数码判断条件之一:如果逆序数为奇数则说明此时无解
    int res=0;
    for(int i=0;i<8;i++)
        for(int j=i+1;j<8;j++)
            if(x[i]>x[j]) res++;
            
    if(res%2) std::cout<<"unsolvable"<

你可能感兴趣的:(算法提高,算法,c++,图论)