邻接表实现 单源最短路径SPFA算法

来源:互联网 发布:网络结婚主持台词 编辑:程序博客网 时间:2024/05/21 22:48

首先讲邻接表的实现,以前一直遇到题目都是用vector模拟,今天遇到一个题目vector超时,于是学习了用数组模拟实现邻接表,新学的数据结构,搞的不是很透彻,记录一下。

其实就是头插法,首先用一个结构体E记录节点的信息,指向那个节点,以及指向节点的权值等信息,给E结构体设置一个next,让它指向H数组,H数组初始化为-1,初始化为-1是为了方便判断某个点直接相连点是否找完了,自己还不是很透彻了,等搞透彻了详细解释,先把实现放在这里方便以后参考,当然H也可以写成结构体形式,写成数组较简单。

[cpp] view plain copy
 print?
  1. int H[N];  //存头节点  
  2. struct   //记录节点信息  
  3. {  
  4.     int v;  
  5.     int count;  
  6.     int next;  
  7. }E[N];  
  8. int T,n,m,top;  
  9. void Readmap(int m)  //读图  
  10. {  
  11.     memset(H,-1,sizeof(H));  
  12.     int top=0;  
  13.     for(int i=0;i<m;i++){  
  14.         scanf("%d%d%d",&x[i],&y[i],&c[i]);  
  15.         E[top].v=y[i];E[top].count=c[i];  
  16.         E[top].next=H[x[i]];  
  17.         H[x[i]]=top++;  
  18.     }  
  19. }  


讲完了邻接表的实现就可以实现SPFA算法了,算法的全称是:Shortest Path Faster Algorithm。

其求最短路径还是相对比较快速的,最主要是比较好写,结合邻接表实现起来非常简单,相对于dijkstra 算法来说首先它能够求解给定的图存在负权边,而dijkstra 算法是不能求解的,所以SPFA就好用多了。

这是dijkstra 算法以及Floyd算法的讲解:http://blog.csdn.net/y990041769/article/details/8524903

SPFA说白了就是一个不断更新的一个过程,官方说法叫松弛,比如说左边的图中,一个无向图,4个顶点的联通性以及各边的权值如图所示,要求从1点开始遍历所有点的最短路径,那么可以借助一个队列,首先顶一个数组sp初始化为无穷大,sp【1】=0,找和1直接相连的点,1--》2权值为1,比sp【2】的值大,松弛sp【2】=1,入队,找一下个与1直接相邻的,第一轮可以得到:

sp【2】=1;   sp【3】=1;  sp【4】=4;

,第一轮你松弛完毕,第二轮开始,从队列中出队元素,得到:

sp【4】=3,从1--》2--》4过来

sp【4】=2,从1--》3-->4过来

其他的点不满足松弛的条件,所以上面结果就是最优的,那么从1开始的最短路就是sp【1--》4】的值得和 0 + 1 + 1 + 2 = 4.

这就是一个SPFA算法的求解过程,可以证明在一个无圈图中最多经过(n-1)轮操作可以得到最优结果,其中n是顶点的数目,今天一个队员一直搞不明白为什么是n-1次,其实就是一个图如果是一条直线,其他点都在这个直线上的话就要进行(n-1)松弛,其实实际上远远小于这个数目。

下面是代码实现模板。

首先是邻接表版:

[cpp] view plain copy
 print?
  1. long long SPFA(int st)  
  2. {  
  3.     for(int i=1;i<=n;i++)  
  4.         sp[i]=inf;  
  5.     sp[1]=0;  
  6.     queue<int> q;  
  7.     q.push(st);  
  8.     while(!q.empty())  
  9.     {  
  10.         int kai=q.front();q.pop();  
  11.         for(int i=H[kai];i!=-1;i=E[i].next)  
  12.         {  
  13.             if(sp[E[i].v]>E[i].count+sp[kai]){  
  14.                 sp[E[i].v]=E[i].count+sp[kai];  
  15.                 q.push(E[i].v);  
  16.             }  
  17.         }  
  18.     }  
  19.     long long ans=0;  
  20.     for(int i=1;i<=n;i++)  
  21.         ans+=sp[i];  
  22.     return ans;  
  23. }  

然后是邻接矩阵版本:其中used数组记录是否访问,pre数据记录路径、

[cpp] view plain copy
 print?
  1. void spfa(int s,int dis[])  
  2. {  
  3.     int i,pre[N];  
  4.     bool used[N];  
  5.     queue<int> q;  
  6.     memset(used,0,sizeof(used));  
  7.     memset(pre,-1,sizeof(pre));  
  8.     for(i=0; i<N; i++)  
  9.         dis[i]=inf;  
  10.     dis[s]=0;  
  11.     used[s]=true;  
  12.     q.push(s);  
  13.     while(!q.empty())  
  14.     {  
  15.         int u=q.front();  
  16.         q.pop();  
  17.         used[u]=false;  
  18.         for(i=0; i<map[u].size(); i++)  
  19.         {  
  20.             Node p=map[u][i];  
  21.             if(dis[p.v]>dis[u]+p.len)  
  22.             {  
  23.                 dis[p.v]=dis[u]+p.len;  
  24.                 pre[p.v]=u;  
  25.                 if(!used[p.v])  
  26.                 {  
  27.                     used[p.v]=true;  
  28.                     q.push(p.v);  
  29.                 }  
  30.             }  
  31.         }  
  32.     }  
  33. }  

应用:

1)判环

假如一个路径中存在环路,可以用这个算法判环,具体方法是加一个cnt 数组,记录每个点松弛的次数,如果松弛次数大于n。则说明存在环路。

题目:http://acm.nyist.NET/JudgeOnline/problem.PHP?pid=973


2)

求最短路径,方法上面讲过

poj1511,

顺便说一道相关题目,这道题是给出一个有向图,求从1点出发的最短路径和回到一点的最短路之和,其实就是先从1一次SPFA,然后把图中边反向在从1进行一次SPFA,题目数据卡的很严,首先结果要用long long,然后初始化最大值一定要足够大,后台有很大的数据,卡了两次,这道题目也可是用dijkstra 算法+优先队列优化过了。好了,就这样吧,累了一天了。

题目代码附上:

[cpp] view plain copy
 print?
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <cstring>  
  4. #include <cstdio>  
  5. #include <queue>  
  6. const int N = 1001000;  
  7. #define inf 10000000000LL  
  8. using namespace std;  
  9. int x[N],y[N],c[N],sp[N];  
  10. int H[N];  //存头节点  
  11. struct   //记录节点信息  
  12. {  
  13.     int v;  
  14.     int count;  
  15.     int next;  
  16. }E[N];  
  17. int T,n,m,top;  
  18. void Readmap(int m)  //读图  
  19. {  
  20.     memset(H,-1,sizeof(H));  
  21.     int top=0;  
  22.     for(int i=0;i<m;i++){  
  23.         scanf("%d%d%d",&x[i],&y[i],&c[i]);  
  24.         E[top].v=y[i];E[top].count=c[i];  
  25.         E[top].next=H[x[i]];  
  26.         H[x[i]]=top++;  
  27.     }  
  28. }  
  29. long long SPFA(int st)  
  30. {  
  31.     for(int i=1;i<=n;i++)  
  32.         sp[i]=inf;  
  33.     sp[1]=0;  
  34.     queue<int> q;  
  35.     q.push(st);  
  36.     while(!q.empty())  
  37.     {  
  38.         int kai=q.front();q.pop();  
  39.         for(int i=H[kai];i!=-1;i=E[i].next)  
  40.         {  
  41.             if(sp[E[i].v]>E[i].count+sp[kai]){  
  42.                 sp[E[i].v]=E[i].count+sp[kai];  
  43.                 q.push(E[i].v);  
  44.             }  
  45.         }  
  46.     }  
  47.     long long ans=0;  
  48.     for(int i=1;i<=n;i++)  
  49.         ans+=sp[i];  
  50.     return ans;  
  51. }  
  52. int main()  
  53. {  
  54.     scanf("%d",&T);  
  55.     while(T--)  
  56.     {  
  57.         long long ans=0;  
  58.         scanf("%d%d",&n,&m);  
  59.         Readmap(m);  
  60.         int u=1;  
  61.         ans+=SPFA(u);  
  62.         top=0;  
  63.         memset(E,0,sizeof(E));  
  64.         memset(H,-1,sizeof(H));  
  65.         for(int i=0;i<m;i++)  
  66.         {  
  67.             E[top].v=x[i];  
  68.             E[top].count=c[i];  
  69.             E[top].next=H[y[i]];  
  70.             H[y[i]]=top++;  
  71.         }  
  72.         ans+=SPFA(u);  
  73.         printf("%lld\n",ans);  
  74.     }  
  75.     return 0;  
  76. }  

3)求最长路。初始为0,往大松弛。
阅读全文
0 0