HDU-1874【最短路的算法Dijkstra和SPFA(链式前向星)】

最短路径问题

引言:
最短路大家都不陌生吧,他可以是两点之间的最短路(Floyd),也可以是单源最短路,即一个点到其他点的最短距离(Dijkstra和SPFA)。Floyd对于n个顶点(n>100)的图,就会T,所以我们经常用后面两种算法。

相关题目:
HDU-1874:畅通工程续
HDU-1874【最短路的算法Dijkstra和SPFA(链式前向星)】_第1张图片
这道题吧,随便一种方法都能过…很神奇。

接下来是三种方法解这道题:

1.Floyd:

for(int k=0;k<n;k++){
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				if(mp[i][j]>mp[i][k]+mp[k][j]) mp[i][j]=mp[i][k]+mp[k][j];
			}
		}
	} 

就是找一个中间点k,如果由i到k,再经过j的距离比直接从i到j的短,就更新mp[i][j]的值,很暴力的O(n^3)。

2.Dijkstra:
他的思想就是疏松边,dis数组存储起点到其他点的最短距离,初始化为inf。题目的坑点是用这个方法,要考虑重边的情况,这时候取最小的那一条。对于本算法,每次取最小dis值得那个点进行访问,我们每访问过一个点就要标记为一,然后对他延申出来的边进行疏松。

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define maxn 20005
const int inf=0x3f3f3f3f;
typedef long long ll;
int main(){
    int n,m,mp[205][205],vis[205],dis[205];
    while(~scanf("%d%d",&n,&m)){
        int u,v,w,x,y,kmin,k;
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(i!=j) mp[i][j]=inf;
                else mp[i][j]=0;
            }
        }
        memset(vis,0,sizeof vis);
        for(int i=0;i<n;i++) dis[i]=inf;
        while(m--){
            scanf("%d%d%d",&u,&v,&w);
            mp[u][v]=min(mp[u][v],w);
            mp[v][u]=min(mp[v][u],w);
        }
        scanf("%d%d",&x,&y);
        dis[x]=0;
        for(int i=0;i<n;i++){
            kmin=inf;
            for(int j=0;j<n;j++){
                if(vis[j]==0&&dis[j]<kmin){
                    kmin=dis[j];
                    k=j;
                }
            }
            vis[k]=1;
            for(int j=0;j<n;j++){
                if(!vis[j]&&dis[j]>dis[k]+mp[k][j]){
                //判断vis[j]等不等于0是达到一种剪枝得效果
                    dis[j]=dis[k]+mp[k][j];
                }
            }
        }
        if(dis[y]!=inf) printf("%d\n",dis[y]);
        else printf("-1\n");
    }
    return 0;
}

3.链式前向星+SPFA:
这才是重点,我研究了很久,才理解到了他的大概意思。链式前向星就是一个结构体加一个数组:

int cnt;
struct edge{
    int w;
    int to;
    int next;
}eg[maxn*3];
int head[maxn];

然后这个结构体,存储的是边,w是边的权值,to表示以i为起点的边的终点,next表示同起点的下一条边,head表示以i为起点的第一条边存储的位置,其实就是我们输入的边的最后一条以i为起点的编号。为什么呢?,因为我们每次输入u,v,w,都要调用addedge函数。即:

for(int i=0;i<m;i++){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            addedge(v,u,w);
        }

链式前向星核心函数:

void addedge(int u,int v,int w){
    eg[cnt].w=w;
    eg[cnt].to=v;
    eg[cnt].next=head[u];
    head[u]=cnt;
    cnt++;
}

推荐一个详细的大佬的链式向前星:看大佬的链式向前星请点击这里

接下来我们回到SPFA算法:

这个算法就是每次将一个点放入队列中,然后将与他邻接的点对应的边进行疏松,若该点不在队列中,就将他入队,并标记vis[i]=1。访问完了之后,注意要将首元素出队,并解除标记,即:vis[i]=0!!!这也是跟Dijkstra最大的区别。因为你下次疏松仍然可以用到这个点。

完整代码:

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define maxn 1005
const int inf=0x3f3f3f3f;
typedef long long ll;
int cnt;
struct edge{
    int w;
    int to;
    int next;
}eg[maxn*3];
int head[maxn];
void addedge(int u,int v,int w){
    eg[cnt].w=w;
    eg[cnt].to=v;
    eg[cnt].next=head[u];
    head[u]=cnt;
    cnt++;
}
int main(){
    int n,m,u,v,w,sx,sy,dis[maxn],vis[maxn],k,t;
    while(scanf("%d%d",&n,&m)!=EOF){
        cnt=0;
        for(int i=0;i<n;i++){
            vis[i]=0;
            dis[i]=inf;
            head[i]=-1;
        }
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            addedge(v,u,w);
        }
        scanf("%d%d",&sx,&sy);
        dis[sx]=0;
        queue<int> q;
        q.push(sx);
        vis[sx]=1;
        while(!q.empty()){
            k=q.front();
            q.pop();
            for(int i=head[k];i!=-1;i=eg[i].next){
                int id=eg[i].to;
                if(dis[id]>dis[k]+eg[i].w){
                    dis[id]=dis[k]+eg[i].w;
                    if(!vis[id]){
                        vis[id]=1;
                        q.push(id);
                    }
                }
            }
            vis[k]=0;
        }
        if(dis[sy]==inf) printf("-1\n");
        else printf("%d\n",dis[sy]);
    }
    return 0;
}

你可能感兴趣的:(c语言算法和模板题目)