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 
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<

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

Dijkstra:

/*Dijstra相对于Floyd效率还是高了很多的,它的主题思想为
从源点出发,找距离它最短的一个点,再以这个点为跳板,找离跳板最近的点
当不能继续进行时,再次回到源点,找第二小的点,重复操作,直到所有的点
都被访问过为止,注意,每个点只能访问一次,不然会和Floyd一样复杂*/ 
#include 
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<

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

/*Bellman_Ford的主体思想为对每一条边进行松弛操作,并且是重复进行,最多  
重复n-1次,如果n-1后还可以进行松弛,说明有负权的存在,但是它并不能解决  
负权问题*/  
#include   
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<

 

 

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

 

 

/*SPFA相比较于之前的写法减少了许多不必要的计算,其主体思路为,首先先将起点
放入队列,然后将它拿出,对其他点进行松弛,如果松弛成功,则将其放进队列(前提是它不在队列中)
每一次都把队首的点拿出来进行松弛,直到队列为空,注意,点可以重复入队,如果入队超过n次,说明有
负权*/ 
#include 
#include 
#include 
using namespace std;
#define inf 0x3f3f3f3f
int m,n;
int map[105][105],dis[105],vis[105],num[105];  //vis表示是否在队列中,num表示入队的次数 
queue 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<

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

/*这种写法的优势在于可以节省大量的空间,用到了结构体,会用到一个head
数组,初始化都为-1,代表没有点与该点相连,如果有点与它相连,那就让head
指向它,然后让这个点的next(结构体变量)指向起点即-1,代表该点是最后一个
和头点相接的点,如果有新的点进来,就把它插入到两者之间,让后进来的点指向
先进来的点,头点(起点)指向新进来的点,这样,最后一个点的next值一定为-1
这样可以对每个边进行松弛*/ 
#include 
#include 
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 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<

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

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

/*Dijkstra优先队列的优化,运用了重载函数可以优先使最大值或者最小值
出队列的原理进行优化,节省了大量的空间和时间,主体思路为用vector分别
存储边和权,用结构体中的元素做跳板,操作过程为,先将起点放入优先队列(先放到结构体中), 
然从该点开始找它可以松弛的点,然后全部暂时存到结构体中,然后放入优先队列中,
然后将起点拿出,优先队列自动接着会吧最小的拿出来进行松弛,直到队列为空,
注意,一个点只能入队一次*/ 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int n,m,dis[105],vis[105];
vector e[2*max+10];//边     
vector 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

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; idis[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<

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

#include 
#include 
#include 
#include 
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 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]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<

 

你可能感兴趣的:(ACM)