算法总结:最短路

来源:互联网 发布:斐波那契java 编辑:程序博客网 时间:2024/06/05 15:59
最短路

一、引入

给定两点之间有多条路径,且长度不一定都相等,让你求其最短的那条路径即为最短路问题。

解决最短路问题的算法有以下三种:

1.     Dijkstra

2.     SPFA

3.     Floyd

二、算法介绍

      1Dijkstra

u      从一个点出发,到达其他顶点的最短路径的长度。

u    基本操作:松弛

u    d[u]+map[u, v]< d[v]这样的边(u,v)称为紧的(tense),可以对它进行松弛(relax):         d[v] = d[u]+w

u    最开始给每一个点一个很大的d值,从d[s]=0开始,不断的对可以松弛的点进行松弛,不能松弛的时候已经求出了最短路了

 

2SPFA

SPFAShortest Path Faster Algorithm)(队列优化)算法是求单源最短路径的一种算法,在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。

设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,而且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

3Floyd

用一个数组记录每一对顶点的距离,然后遍历每一个点,让其做中点,判断是否可以通过这个点让某对顶点的距离更小,如果可以则更新该对顶点的距离。

三、算法实现

1Dijkstra

²     Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止

²   注意该算法要求图中不存在负权边。

²   可以证明,具有最小的d[i](临时最短路)值的(还没加入最短路)点在此以后无法松弛

²   所以每次找最近的点进行松弛操作

1、在开始之前,认为所有的点都没有进行过计算,dis[]全部赋值为极大值(dis[]表示各点当前到源点的最短距离)

2、源点的dis值明显为0

3、还没算出最短路的点中dis[]最小的一个点u,其最短路就是当前的dis[u]

4、松弛操作:对于与u相连的所有点v,若dis[u]+cost[u][v]比当前的dis[v],更新dis[v]

5、重复3,4直到源点到所有点的最短路都已求出

代码实现:

void dijkstra(int a){      int i,vis[105];      for(i=1;i<=n;i++)//初始化dis[],vis[]      {           dis[i]=max;           vis[i]=0;      }      dis[a]=0;      while(1)      {           int v=-1;           for(i=1;i<=n;i++)                 if(!vis[i]&&(v==-1||dis[i]<dis[v]))                      v=i;           if(v==-1)//所有路径均已标记完毕                 break;           vis[v]=1;//标记           for(i=1;i<=n;i++)                 dis[i]=min(dis[i],dis[v]+cost[v][i]);//更新dis[]      }}


       2、SPFA

SPFA 在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。

       代码实现:

void spfa(int sx){      queue<int>q;      memset(dis,INF,sizeof(dis));      memset(vis,0,sizeof(vis));      q.push(sx);      dis[sx]=0;      vis[sx]=1;      while(!q.empty())      {           int u=q.front();           q.pop();           vis[u]=0;           for(int i=head[u];i!=-1;i=A[i].next)           {                 int v=A[i].to;                 if(dis[v]>dis[u]+A[i].val)                 {                      dis[v]=dis[u]+A[i].val;                      if(!vis[v])                      {                            vis[v]=1;                            q.push(v);                      }                 }           }      }}


3Floyd

假设有这么一个图:

包含顶点1,2,3,4以及两个点的距离,

for( k=1; k<=n ; k++ ) 这个k就是从第一个顶点到最后一个顶点依次

当做中间节点,然后i从第一个点到最后一个点的遍历,更新到j

的距离,如果i从k点到j点的距离小于i直接到j点的距离,那

么就把i到j的距离更新为,i从k到j的距离。上边代码的dist就

存放i到j的最短距离。

例如上边的图中:

当2为中间节点的时候,1直接到4的距离是8,但是1从2到4

的距离是7,所以当2为中间节点的时候dis[1][4]就等于7,同理3

到4的距离是8。

 

这就是floyd三层for循环的意思和作用,每个点当做中间节点依次

更新为路径更短的值,最后就把任意两点的最短距离得到。

初始化为:

 

for( i=1;i<=n;i++ )for(j=1; j<=n; j++){If(i==j) dist[i][j] = 0;else  dist[i][j] = inf;}


核心代码实现:

void Floyd(){     int i,j,k;     for( k=1 ; k<=n ; k++ )         for( i=1 ; i<=n ; i++ )             for( j=1 ; j<=n ; j++ )                 if(  dist[i][k] + dist[k][j] < dist[i][j] )                     dist[i][j] = dist[i][k] + dist[k][j];}


四、例题解析

       例一:HDU 1874畅通工程续

Problem Description

某省自从实行了很多年的畅通工程计划后,终于修建了很多路。不过路多了也不好,每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。这让行人很困扰。

现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。

 

 

Input

本题目包含多组数据,请处理到文件结束。
每组数据第一行包含两个正整数NM(0<N<200,0<M<1000),分别代表现有城镇的数目和已修建的道路的数目。城镇分别以0N-1编号。
接下来是M行道路信息。每一行有三个整数A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城镇A和城镇B之间有一条长度为X的双向道路。
再接下一行有两个整数S,T(0<=S,T<N),分别代表起点和终点。

 

 

Output

对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从ST的路线,就输出-1.

 

 

Sample Input

3 3

0 1 1

0 2 3

1 2 1

0 2

3 1

0 1 1

1 2

 

 

Sample Output

2

-1

 

       题目大意:某省修建了很多路。不过每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。

参考代码:

1、      Dijkstra

#include<cstdio>#include<cstring>#define max 0x3f3f3f3f#define min(a,b) (a>b?b:a)int dis[205],cost[205][205],n;void dijkstra(int a)//模板{int vis[205];for(int i=0;i<n;i++){      dis[i]=max;      vis[i]=0;}dis[a]=0;while(1){      int v=-1;      for(int i=0;i<n;i++)            if(!vis[i]&&(v==-1||dis[i]<dis[v]))                  v=i;      if(v==-1)            break;      vis[v]=1;      for(int i=0;i<n;i++)            dis[i]=min(dis[i],dis[v]+cost[v][i]);}}int main(){int m,a,b,i,j,c;while(~scanf("%d%d",&n,&m)){      for(i=0;i<n;i++)//初始化            for(j=i;j<n;j++)            cost[i][j]=cost[j][i]=max;      while(m--)      {            scanf("%d%d%d",&a,&b,&c);            cost[a][b]=cost[b][a]=min(cost[a][b],c);//可能有重边      }      scanf("%d%d",&a,&b);      dijkstra(a);      if(dis[b]==max)            printf("-1\n");      else            printf("%d\n",dis[b]);}return 0;}


2、SPFA

#include<queue>#include<cstring>#define INF 0x3f3f3f3fusing namespace std;int dis[205],vis[205],head[1005],num,k;struct node//定义结构体{int from,to,val,next;}A[1005]; void chan(int a,int b,int c)//给相连通的两个城镇赋值{node e={a,b,c,head[a]};A[num]=e;head[a]=num++;} void spfa(int sx)//模板{queue<int>q;memset(dis,INF,sizeof(dis));memset(vis,0,sizeof(vis));q.push(sx);vis[sx]=1;dis[sx]=0;while(!q.empty()){      int u=q.front();      q.pop();      vis[u]=0;      for(int i=head[u];i!=-1;i=A[i].next)      {            int v=A[i].to;            if(dis[v]>dis[u]+A[i].val)            {                  dis[v]=dis[u]+A[i].val;                  if(!vis[v])                  {                       vis[v]=1;                       q.push(v);                  }            }      }}if(dis[k]==INF)      printf("-1\n");else      printf("%d\n",dis[k]);}int main(){int n,m,a,b,f,c;while(~scanf("%d%d",&n,&m)){      num=0;      memset(head,-1,sizeof(head));//初始化      while(m--)      {            scanf("%d%d%d",&a,&b,&c);            chan(a,b,c);            chan(b,a,c);      }      scanf("%d%d",&f,&k);      spfa(f);}return 0;}


3、            Floyd

#include<cstdio>#define INF 0x3f3f3f3fint s[205][205],n;void floyd()//模板{int k,i,j;for(k=0;k<n;k++)      for(i=0;i<n;i++)            for(j=0;j<n;j++)                  if(s[i][k]+s[k][j]<s[i][j])                       s[i][j]=s[i][k]+s[k][j];}int main(){int m,i,j,a,b,c,d,t;while(~scanf("%d%d",&n,&m)){      for(i=0;i<=n;i++)//初始化            for(j=0;j<=n;j++)                  s[i][j]=INF;      while(m--)      {            scanf("%d%d%d",&a,&b,&c);            if(c<s[a][b])//防重边                  s[a][b]=s[b][a]=c;      }      scanf("%d%d",&d,&t);      if(d==t)      {            printf("0\n");            continue;      }      floyd();      if(s[d][t]<INF)            printf("%d\n",s[d][t]);      else            printf("-1\n");}return 0;}


例二:HDU- 1869六度分离

Problem Description

1967年,美国著名的社会学家斯坦利·米尔格兰姆提出了一个名为小世界现象(small world phenomenon)”的著名假说,大意是说,任何2个素不相识的人中间最多只隔着6个人,即只用6个人就可以将他们联系在一起,因此他的理论也被称为六度分离理论(six degrees of separation)。虽然米尔格兰姆的理论屡屡应验,一直也有很多社会学家对其兴趣浓厚,但是在30多年的时间里,它从来就没有得到过严谨的证明,只是一种带有传奇色彩的假说而已。

Lele
对这个理论相当有兴趣,于是,他在HDU里对N个人展开了调查。他已经得到了他们之间的相识关系,现在就请你帮他验证一下六度分离是否成立吧。

 

 

Input

本题目包含多组测试,请处理到文件结束。
对于每组测试,第一行包含两个整数N,M(0<N<100,0<M<200),分别代表HDU里的人数(这些人分别编成0~N-1),以及他们之间的关系。
接下来有M行,每行两个整数A,B(0<=A,B<N)表示HDU里编号为A和编号B的人互相认识。
除了这M组关系,其他任意两人之间均不相识。

 

 

Output

对于每组测试,如果数据符合六度分离理论就在一行里输出"Yes",否则输出"No"

 

 

Sample Input

8 7

0 1

1 2

2 3

3 4

4 5

5 6

6 7

8 8

0 1

1 2

2 3

3 4

4 5

5 6

6 7

7 0

 

 

Sample Output

Yes

Yes

 

题目大意:你帮忙验证一下六度分离是否成立,即任何2个素不相识的人中间最多只隔着6个人,即只用6个人就可以将他们联系在一起。

参考代码:

1、            Dijkstra

#include<cstdio>#define max 0x3f3f3f3f#define min(a,b) (a>b?b:a)int dis[101],n,cost[101][101];void dijkstra(int a)//模板{int vis[101],i;for(i=0;i<n;i++){      dis[i]=max;      vis[i]=0;}dis[a]=0;while(1){      int v=-1;      for(i=0;i<n;i++)            if(!vis[i]&&(v==-1||dis[i]<dis[v]))                  v=i;      if(v==-1)            break;      vis[v]=1;      for(i=0;i<n;i++)            dis[i]=min(dis[i],dis[v]+cost[v][i]);}}int main(){int m,j,i,a,b;while(~scanf("%d%d",&n,&m)){      for(i=0;i<n;i++)//初始化            for(j=0;j<n;j++)                  cost[i][j]=max;      while(m--)      {            scanf("%d%d",&a,&b);            cost[a][b]=cost[b][a]=1;      }      int flag=1;      for(i=0;i<n;i++)      {            dijkstra(i);                  for(j=0;j<n;j++)                  {                       if(dis[j]>7)                       {                             flag=0;                             break;                       }                      }            if(flag==0)                  break;              }      if(!flag)            printf("No\n");       else            printf("Yes\n");}return 0;}


2、            SPFA:

#include<cstdio>#include<queue>#include<cstring>#define INF 0x3f3f3f3fusing namespace std;int head[205*2],num,n,dis[105],vis[105];struct node{int from,to,val,next;}A[205*2]; void chan(int a,int b){node e={a,b,1,head[a]};A[num]=e;head[a]=num++;} int spfa(int sx){memset(dis,INF,sizeof(dis));memset(vis,0,sizeof(vis));queue<int>q;q.push(sx);dis[sx]=0;vis[sx]=1;while(!q.empty()){      int u=q.front();      q.pop();      vis[u]=0;      for(int i=head[u];i!=-1;i=A[i].next)      {            int v=A[i].to;            if(dis[v]>dis[u]+A[i].val)            {                  dis[v]=dis[u]+A[i].val;                  if(!vis[v])                  {                       vis[v]=1;                       q.push(v);                  }            }      }}for(int i=0;i<n;i++)      if(dis[i]>7)            return 1;return 0;}int main(){int m,a,b,i;while(scanf("%d%d",&n,&m)!=EOF){      num=0;      memset(head,-1,sizeof(head));      while(m--)      {            scanf("%d%d",&a,&b);            chan(a,b);            chan(b,a);      }      for(i=0;i<n;i++)      {            if(spfa(i))                  break;      }      if(i==n)            printf("Yes\n");      else            printf("No\n"); }return 0;}


3、            Floyd:

#include<cstdio>#define INF 0x3f3f3f3fint s[100][100];int main(){int n,m,i,j,k,a,b;while(~scanf("%d%d",&n,&m)){      for(i=0;i<n;i++)            for(j=0;j<n;j++)                  s[i][j]=INF;      while(m--)      {            scanf("%d%d",&a,&b);            s[a][b]=s[b][a]=1;      }      for(k=0;k<n;k++)            for(i=0;i<n;i++)                  for(j=0;j<n;j++)                       if(s[i][j]>s[i][k]+s[k][j])                             s[i][j]=s[i][k]+s[k][j];      int flag=0;      for(i=0;i<n;i++)            for(j=i+1;j<n;j++)                  if(s[i][j]>7)                  {                       flag=1;                       break;                  }      if(!flag)            printf("Yes\n");      else            printf("No\n");            }return 0;}


五、相关例题

以下题目适用于初学者,大牛们可跳过~~

HDU - 1385 Minimum Transport Cost

HDU - 3790最短路径问题

HDU - 2112 HDU Today

POJ 3259 -- Wormholes

 

0 0
原创粉丝点击