hdu 2544(所有最短路算法总结)

最短路

 

Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 82480    Accepted Submission(s): 35690

 

 

Problem Description

在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?
 

 

 

Input

输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。
输入保证至少存在1条商店到赛场的路线。

 

 

Output

对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间

 

 

Sample Input

2 11 2 33 31 2 52 3 53 1 20 0

 

Sample Output

32

 

 

 

 跟这道题打了三天架,终于把所有最短路算法总结到一起,话不多说,来一波代码。

    Floyd:

/*Floyd是十分暴力的一个算法,复杂度相当高,但是十分简单,并且可以解决负权问题,
其主体思想为让每个点都做一次跳板,去松弛所有的,这样可以求出来任意两个点之间
的最短路,用邻接矩阵实现*/ 
#include <iostream>
using namespace std;
#define inf 0x3f3f3f3f     //概念上的无穷大 
int n,m,map[105][105];     //邻接矩阵存地图 
int main()
{
    int i,j,k;
    int x,y,z;
    while(cin>>n>>m && n || m)
    {
        for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
        {
            if(i==j)
            map[i][j]=0;
            else
            map[i][j]=inf;        //初始化 
        }
        
        for(i=1;i<=m;i++)
        {
            cin>>x>>y>>z;
            if(map[x][y]>z)      //解决重边的问题 
            {
                map[x][y]=map[y][x]=z;     //构造无向图 
            }
        }
        for(k=1;k<=n;k++)         //每个点都做一次跳板 
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                if(map[i][j]>map[i][k]+map[k][j])      //对图中任意所有的点进行松弛 
                map[i][j]=map[i][k]+map[k][j];
            }
        }
        cout<<map[1][n]<<endl;       //直接输出map[strat][end]就是最短路 
    }
    return 0;
}

不难看出这个的效率是十分低的,那让我们来看另一种算法

Dijkstra:

/*Dijstra相对于Floyd效率还是高了很多的,它的主题思想为
从源点出发,找距离它最短的一个点,再以这个点为跳板,找离跳板最近的点
当不能继续进行时,再次回到源点,找第二小的点,重复操作,直到所有的点
都被访问过为止,注意,每个点只能访问一次,不然会和Floyd一样复杂*/ 
#include <iostream>
using namespace std;
#define inf 0x3f3f3f3f
int map[105][105],dis[105],vis[105];     //map存地图,dis存源点到当前点的距离,vis存访问状态 
int n,m;
void dijkstra(int start) 
{
    int i,j,k;
    for(i=1; i<=n; i++)
    {
        dis[i]=map[start][i];      //对dis进行初始化 
    }
    for(i=1;i<=n;i++)
    vis[i]=0;            //0表示没有被访问,1表示被访问 
    vis[start]=1;
    for(i=1;i<=n-1;i++)   //因为最多访问n-1个点,所以循环n-1次 
    {
        int mix=inf;
        int u;
        for(j=1;j<=n;j++)
        {
            if(vis[j]==0 && mix>dis[j])
            {
                mix=dis[j];          //记录下距离源点最近的点,并且没有被访问 
                u=j; 
            }
        }
        vis[u]=1;      //标记为已经被访问 
        for(k=1;k<=n;k++)
        {
            if(vis[k]==0 && dis[k]>dis[u]+map[u][k])
            dis[k]=dis[u]+map[u][k];            //以该点为跳板,对所有点进行松弛 
        }
    }
}
int main()
{
    int i,j,k;
    while(cin>>n>>m && n || m)
    {
        for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
        {
            if(i==j)
            map[i][j]=0;
            else
            map[i][j]=inf;     //初始化 
        }
        while(m--)
        {
            int x,y,z;
            cin>>x>>y>>z;
            if(map[x][y]>z)
            {
                map[x][y]=map[y][x]=z;     //无向图 
            }
        }
        dijkstra(1);
        cout<<dis[n]<<endl;
    }
    return 0;
}

Dijkstra算法虽然好,但是并不能解决负权问题,更准确的说是判断有没有负权的存在,于是就有了另一种算法,Bellman_Fod:

/*Bellman_Ford的主体思想为对每一条边进行松弛操作,并且是重复进行,最多  
重复n-1次,如果n-1后还可以进行松弛,说明有负权的存在,但是它并不能解决  
负权问题*/  
#include <iostream>  
using namespace std;  
#define inf 0x3f3f3f3f  
#define max 10000  
int u[2*max+10],v[2*max+10],w[2*max+10],dis[105];  
int main() {  //u存起点,v存终点,w存权值,dis和迪杰一样,由于是无向图,所以要 *2(我在这里犯了一次错)  
    int n,m,i,j,k;  
    int x,y,z;  
    while(cin>>n>>m && n || m) {  
        for(i=1; i<=2*m; i++) {  
            cin>>x>>y>>z;  
            u[i]=x;  
            v[i]=y;  
            w[i]=z;  
            j=i+1;       //构造无向图  
            u[j]=y;  
            v[j]=x;  
            w[j]=z;  
            i++;  
        }  
        for(i=1; i<=n; i++)  
            dis[i]=inf;  
        dis[1]=0;     //将起点设置为零,这样可以保证从起点开始松弛  
        for(k=1; k<=n-1; k++) { //最多循环n-1次  
            for(i=1; i<=2*m; i++) {  
                if(dis[v[i]]>dis[u[i]]+w[i])  
                    dis[v[i]]=dis[u[i]]+w[i];     //对所有的边进行松弛操作  
            }  
        }  
        for(i=1; i<=2*m; i++) {  
            if(dis[v[i]]>dis[u[i]]+w[i])  
                return false;             //这里检测有没有负权,不过本题用不到   
        }  
        cout<<dis[n]<<endl;  
    }  
    return 0;  
}  

 

 

看到这里我想大家一定也发现了一个问题,写法太朴素了,而且多了好多不必要的计算,那么,我们就有对它们的优化,首先,先来看一下邻接矩阵版本的Bellman_Ford的队列优化,即SPFA 1.0:

 

 

/*SPFA相比较于之前的写法减少了许多不必要的计算,其主体思路为,首先先将起点
放入队列,然后将它拿出,对其他点进行松弛,如果松弛成功,则将其放进队列(前提是它不在队列中)
每一次都把队首的点拿出来进行松弛,直到队列为空,注意,点可以重复入队,如果入队超过n次,说明有
负权*/ 
#include <iostream>
#include <queue>
#include <string.h>
using namespace std;
#define inf 0x3f3f3f3f
int m,n;
int map[105][105],dis[105],vis[105],num[105];  //vis表示是否在队列中,num表示入队的次数 
queue<int> q;
int SPFA(int start)
{
    int i,j,k,temp;
    for(i=1;i<=n;i++)
    dis[i]=inf;
    memset(num,0,sizeof(num));
    memset(vis,0,sizeof(vis));
    dis[start]=0;        //将起点初始化为零,保证从这一点开始松弛 
    q.push(start);
    while(!q.empty())
    {
        temp=q.front();     //取队首 
        q.pop();           //拿出 
        for(i=1;i<=n;i++)
        {
            if(dis[i]>map[temp][i]+dis[temp])
            {
                dis[i]=dis[temp]+map[temp][i];   //松弛 
                if(!vis[i])         //入队 
                {
                    q.push(i);
                    vis[i]=1;
                    num[i]++;     //判断是否存在负环 
                    if(num[i]>n)
                    return false;
                }
            }
            vis[temp]=0;     //标记出队 
        }
    }
}
int main()
{
    int i,j,k;
    int a,b,c;
    while(cin>>n>>m && n || m)
    {
        for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
        {
            if(i==j)
            map[i][j]=0;
            else
            map[i][j]=inf;     //初始化 
        }
        for(i=1;i<=m;i++)
        {
            cin>>a>>b>>c;
            if(map[a][b]>c)
            map[a][b]=map[b][a]=c;    //无向图 
        }
        SPFA(1);
        cout<<dis[n]<<endl;
    }
    return 0;
}

虽然说这解决了许多不必要的计算,但是用邻接矩阵是十分浪费空间的,于是就有了SPFA2.0(链式前向星):

/*这种写法的优势在于可以节省大量的空间,用到了结构体,会用到一个head
数组,初始化都为-1,代表没有点与该点相连,如果有点与它相连,那就让head
指向它,然后让这个点的next(结构体变量)指向起点即-1,代表该点是最后一个
和头点相接的点,如果有新的点进来,就把它插入到两者之间,让后进来的点指向
先进来的点,头点(起点)指向新进来的点,这样,最后一个点的next值一定为-1
这样可以对每个边进行松弛*/ 
#include <iostream>
#include <queue>
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int dis[105],head[105],vis[105];
int n,m,ans;
struct node {
    int v,w,next;     //v为要到达的点,w为权值,next为指向的下一个点 
} edge[2*max+5];      //无向图 *2
void add(int from,int to,int cost) {
    edge[ans].v=to;
    edge[ans].w=cost;    //添加新的点 
    edge[ans].next=head[from];       //ans代表第几条边 
    head[from]=ans++;               //head指向最新的点(第几条边) 
}
bool spfa(int start) {
    for(int i=1; i<=n; i++)
        dis[i]=inf;  
    dis[start]=0;        //除源点外,其他均为inf 
    for(int i=1; i<=n; i++)
        vis[i]=0;
    queue<int> q;
    q.push(start);
    vis[start]=1;      //源点入队 
    while(!q.empty()) {
        int temp=q.front();   //出队 
        q.pop();
        vis[temp]=0;
        for(int i=head[temp]; i!=-1; i=edge[i].next) {   //对每个点所指向的点一一进行处理,i=-1时说明已经没有点与之相连 
            int a=edge[i].v;
            int b=edge[i].w;
            if(dis[a]>dis[temp]+b) {
                dis[a]=dis[temp]+b;
                if(vis[a]==0) {
                    q.push(a);
                    vis[a]=1;
                }
            }
        }
    }
}
int main() {
    int x,y,z;
    int i,j,k;
    while(scanf("%d%d",&n,&m) !=EOF && n || m) {
        ans=1;
        for(i=1; i<=n; i++)
            head[i]=-1;
        for(i=1; i<=m; i++) {
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            add(y,x,z);   //无向图 
        }
        spfa(1);
        cout<<dis[n]<<endl;
    }
    return 0;
}

这个一样可以判断是否存在负环,我忘记谢了,操作和1.0一样。

既然Bellman_Ford算法可以进行优化,那么Dijkstra一样进行优化,用到了优先队列:

/*Dijkstra优先队列的优化,运用了重载函数可以优先使最大值或者最小值
出队列的原理进行优化,节省了大量的空间和时间,主体思路为用vector分别
存储边和权,用结构体中的元素做跳板,操作过程为,先将起点放入优先队列(先放到结构体中), 
然从该点开始找它可以松弛的点,然后全部暂时存到结构体中,然后放入优先队列中,
然后将起点拿出,优先队列自动接着会吧最小的拿出来进行松弛,直到队列为空,
注意,一个点只能入队一次*/ 
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int n,m,dis[105],vis[105];
vector<int> e[2*max+10];//边     
vector<int> w[2*max+10];//权         //无向图 *2 
struct P {
    int u,d;
    bool operator <(const P &a) const {
        return d>a.d;      //函数重载实现每次优先拿最小值 
    }
};
void dijkstra(int s) {
    int i,k,j;
    for(i=1; i<=n; i++)
        dis[i]=inf;   //初始化 
    dis[s]=0;
    memset(vis,0,sizeof(vis));
    priority_queue<P> q;
    P tn;
    tn.u=s;
    tn.d=0;   //暂时存到结构体里面,以便优先队列发挥作用 
    q.push(tn);
    while(!q.empty()) {
        P t;
        t=q.top();
        q.pop();
        if(vis[t.u]) continue;    //如果已经访问过,直接取下一个点 
        vis[t.u]=1;
        int u=t.u;        
        for(i=0; i<e[u].size(); i++) {      //和它相连数量为size 
            int v=e[u][i];
            if(dis[v]>dis[u]+w[u][i]) {
                dis[v]=dis[u]+w[u][i];
                tn.d=dis[v];      //暂时存到结构体中,以便放入队列 
                tn.u=v;
                q.push(tn);
            }
        }
    }
}
int main() {
    int i,j,k;
    int a,b,c;
    while(cin>>n>>m && n || m) {
        for(i=0; i<=2*m; i++) {
            e[i].clear();
            w[i].clear();      //一定要初始化,第一次我就因为这个WA了 
        }
        for(i=1; i<=m; i++) {
            cin>>a>>b>>c;
            e[a].push_back(b);
            w[a].push_back(c);
            e[b].push_back(a);    //无向图 
            w[b].push_back(c);
        }
        dijkstra(1);
        cout<<dis[n]<<endl;
    }
    return 0;
}

同样, 它也有链式前向星的写法:

#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
#define inf 0x3f3f3f
long long dis[10005];
int head[10005];
int cnt;
struct node
{
	int v;
	int w;
	int next;
}edge[20005];
struct tnode
{
	int v;//当前点 
	int w;//当前点到起点的距离 
	friend bool operator < (tnode a,tnode b)
	{
		return a.w>b.w;
	}
}temp,now;//用于存储暂时用到的点 
void add(int from,int to,int val)//链式前向星存储 
{
	edge[cnt].v=to;
	edge[cnt].w=val;
	edge[cnt].next=head[from];
	head[from]=cnt++;
}
void dji(int s,int e)
{
	priority_queue<tnode> q;
	temp.v=s;//存起点 
	temp.w=0;
	q.push(temp);
	while(!q.empty())
	{	
		temp=q.top();//每次取距离起点最近的点 
		q.pop();
		int t=temp.v;
		if(dis[t]<temp.w) continue;//如果经过一个过渡点到达该点的距离小于起点直接到达该点的距离,那么已经没有必要再松弛 
		for(int i=head[t];i!=-1;i=edge[i].next)
		{
			now.v=edge[i].v;
			now.w=edge[i].w;
			if(dis[now.v]>dis[temp.v]+now.w)
			{
				dis[now.v]=dis[temp.v]+now.w;
				q.push(now);
			}
		}
	}
}
int main()
{
	int t,n,i,j,k;
	int a,b,c;
	while(cin>>n>>t)
	{
		cnt=1;
		memset(head,-1,sizeof(head));
		memset(dis,0x3f,sizeof(dis));
		dis[n]=0;
		while(t--)
		{
			cin>>a>>b>>c;
			add(a,b,c);
			add(b,a,c);
		}
		dji(1,n);
		cout<<dis[n]<<endl;
	}
	return 0;
}

 

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值