图论1-k短路

相信大家都已经会求最短路了,k短路是在最短路熟练掌握的情况下进行的一个进阶,要求把最短路的过程想的十分通透才能学的懂。

先摆出一道例题做引例:

http://poj.org/problem?id=2449 POJ2449 Remmarguts' Date

大概题意就是求从s到t的第k短路,注意要反着建边。同时如果s==t,什么都不走的这条路不算,也就是if(s==t) k++;

如果大家题都读懂了就往下看。

这是一个十分令人困惑的题,之前学的最短路算法都只能记录一个dis,也就是从s到这个点的最短距离,可我们这次要求的k短路,

我们该怎么办呢?因为SPFA是从bfs扩展出来的,所以我们先从bfs想,如果是bfs的话,那第k短路就是第k次访问t时的路径,但是

SPFA中没法这样做啊,所以要新开一个函数来做这件事。但是能bfs的条件是所有边得权值都一样,但不一样怎么办呢?我们必须

要先扩展代价较小的,但如何确定哪个点的代价小呢?我们必须要一个估价函数,也就是A*(A star),这个估价函数必须要满足

比真实代价小。但是这个估价该怎么算呢,我们会发现可以由现有代价加上这个点到t点的代价就可以是它的估价,满足成为估价的

条件,大概的思路已经理清楚了。现在,我在串一遍整个思路。

1.读入,建图。注意要正反图都要建,因为我们算任意一点到s要建反图,但是A*时要正图,所以我们正反图都要建。

2.反图跑一遍spfa,初始化出所有点到s的代价,以便估价函数运算。

3.正图A*,并找出答案,每个结构体有三个量,分别是x,ans,f,表示当前节点编号,答案,以及估价,priority_queue按估价升序排序即可。

注意:

1.当s==t是k要++.

2.记得输出-1

3.记得要建两个图

下面就是代码了:

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int NR=1e5+10;
const int INF=0x3f3f3f3f;//极大值 
int n,m;
int dis[NR];//算估价时要用,用每个点到t的最小值 
int tot;//边的总数 
int head1[NR],head2[NR];//正反两个图 
bool flag[NR];//spfa的时候判断是否在队列里 
struct edge
{
    int to;
    int w;
    int next;
}e[NR<<1],e2[NR<<1];//邻接表正常操作 
struct Nd
{
    int f,ans,x;
    bool operator < (Nd A)const//按估价升序排列,如果估价相等就看真实的代价 
    {
        if(A.f==f) return ans>A.ans;
        return f>A.f;
    }
};
void add(int u,int v,int w)
{
    tot++;
    e[tot].to=v;
    e[tot].w=w;
    e[tot].next=head1[u];
    head1[u]=tot;
    e2[tot].to=u;
    e2[tot].w=w;
    e2[tot].next=head2[v];
    head2[v]=tot;
    //两遍建图 
}
void spfa(int t)//默认spfa会打 
{
    for(int i=1;i<=n;i++) dis[i]=INF;
    dis[t]=0;
    queue<int> q;
    q.push(t);
    flag[t]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();flag[x]=0;
        for(int i=head2[x];i;i=e2[i].next)
        {
            int y=e2[i].to,w=e2[i].w;
            if(dis[y]>dis[x]+w)
            {
                dis[y]=dis[x]+w;
                if(!flag[y])
                {
                    q.push(y);
                    flag[y]=1;
                }
            }
        }
        
    }
}
int a_star(int s,int t,int k)//进入A*函数 
{
    if(s==t) k++;//如果起终点相同,不能站着不动,就是要去掉一种所以k++ 
    if(dis[s]==INF) return -1;//如果都到不了,就没有扩展的必要了 
    priority_queue<Nd> q;
    int cnt=0;Nd x,to;
    x.x=s;x.ans=0;//初值 
    x.f=x.ans+dis[x.x];//计算出使估价 
    q.push(x);
    while(!q.empty())
    {
        x=q.top();q.pop();
        if(x.x==t) cnt++;//每一次经过终点,就找到了一条路,所以cnt要++ 
        if(cnt==k) return x.ans;//如果找到第k短路了,结束A*函数 
        for(int i=head1[x.x];i;i=e[i].next)
        {
            to.x=e[i].to;
            to.ans=x.ans+e[i].w;
            to.f=to.ans+dis[to.x];
            q.push(to);
            //每次扩展 
        }
    }
    return -1;//没有第k短路输出-1 
}
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*f;
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        add(x,y,z);//建图 
    }
    int s=read(),t=read(),k=read();    
    spfa(t);//从t开始跑最短路 
    int ans=a_star(s,t,k);//得到答案 
    printf("%d",ans);
    return 0;
}

 

posted @ 2020-03-23 16:10  CZD648  阅读(272)  评论(0编辑  收藏  举报
Live2D