1、floyd
核心代码只有五行
for(int k=1; k<=n; k++) for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) { if(map1[i][j]>map1[i][k]+map1[k][j]) map1[i][j]=map1[i][k]+map1[k][j]; }这种算法可以找多源最短路,想知道a点到b点最短路,只能加入中间点来缩短路径,比如a到b 加入中间点k a到k到b
那么我们可以这样判断,要知道i到j的最短路,我们只要判断e[i][j]是否大于e[i][1]+e[1][j]即可,而中间值1则要用for循环从1到n遍历一个遍,就是查找所有中间值
以下为完整代码
//floyd #include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f int main() { int n,m,a,b,c; int map1[100][100]; scanf("%d%d",&n,&m); for(int i=1; i<=n; i++) for(int j=1;j<=n;j++) if(i==j)
map1[i][j]=0; else
map1[i][j]=inf; for(int i=1; i<=m; i++) { scanf("%d%d%d",&a,&b,&c); map1[a][b]=c; } for(int k=1; k<=n; k++) for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) { if(map1[i][j]>map1[i][k]+map1[k][j]) map1[i][j]=map1[i][k]+map1[k][j]; } for(int i=1; i<=n; i++) { for(int j=1;j<=n;j++) printf("%d ",map1[i][j]); printf("\n"); } return 0; }
2、dijkstra
这个算法只能计算单元最短路,而且不能计算负权值,这个算法是贪心的思想,
dis数组用来储存起始点到其他点的最短路,但开始时却是存的起始点到其他点的初始路程。通过n-1遍的遍历找最短。
比如1到3的最短路就是比较dis[3]与dis[2]+e[2][3],如果大于的话就更新dis[3]位dis[2]+e[2][3],这个专业术语叫松弛,这种算法的核心思想就是通过边来松弛一号顶点到其他定点的路程,这也就能解释为什么要遍历n-1遍了。
book数组用来标记,被标记的是已经找过的最短路,没被标记的没有被找过的最短路,当全部找过以后算法结束,也就是说dis数组中的数是起始点到其他所有点的最短路
以下为完整代码
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f int e[100][100],dis[100],book[100];//e用来记录数组,dis用来记录初始点到各个点的位置,book用来标记,被标记的表示已经连接成最短路 int main() { int n,m,t1,t2,t3,u,v,min1; scanf("%d%d",&n,&m); for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) if(i==j)
e[i][j]=0; else
e[i][j]=inf; for(int i=1; i<=m; i++) { scanf("%d%d%d",&t1,&t2,&t3); e[t1][t2]=t3; } for(int i=1; i<=n; i++) dis[i]=e[1][i]; for(int i=1; i<=n; i++) book[i]=0; book[1]=1; for(int i=1; i<=n-1; i++) //更新dis数组 { min1=inf; for(int j=1; j<=n; j++) { if(book[j]==0&&dis[j]<min1)//每次找最短的边 { min1=dis[j]; u=j; } } book[u]=1; for(int v=1; v<=n; v++) { if(e[u][v]<inf) if(dis[v]>dis[u]+e[u][v])//如果从起始点到j的距离大于起始点到u的距离加上u到j的距离就更新,专业术语松弛操作 dis[v]=dis[u]+e[u][v]; } } for(int i=1; i<=n; i++) printf("%d ",dis[i]); return 0; }下边仍为dijkstra算法,不过存地图的方式从邻接矩阵换成了邻接表,邻接表的时间复杂度更低,而且可以存两点之间的多组数据,比如 1点到 2点之间可能存在 2条路,每条路的权值不同先介绍邻接表,这里给出啊哈磊的博客,简单易懂,链接http: //ahalei.blog. 51cto.com/ 4767671/ 1391988个人理解first数组用来存以u[i]为顶点的最后一条边的编号, next数组存编号为i的边的上一条边,如果这个边是以u[i]为顶点的第一条边,则 next[i]为- 1。创立 for(i= 1;i<=n;i++) first[i]=- 1; for(i= 1;i<= m;i++){ scanf( "%d %d %d",&u[i],&v[i],&w[i]); next[i]=first[u[i]]; //next等于编号i的上一条边 first[u[i]]=i;}遍历 for(i= 1;i<=n;i++){ k=first[i]; while(k!=- 1) { printf( "%d %d %d\n",u[k],v[k],w[k]); k= next[k]; }}
这里给到题poj1724,dijs的变形 用了优先队列和邻接表
链接:
http://poj.org/problem?id=1724
直接上代码
#include<stdio.h> #include<string.h> #include<algorithm> #include<queue> using namespace std; #define inf 0x3f3f3f3f const int maxn=10005; int num,first[maxn]; struct node { int id; int len; int val; }node1; struct edge { int id; int len; int val; int next;//作用跟next数组一样 }e[maxn]; priority_queue<node>q; bool operator <(node a,node b) { return a.len>b.len; } void add(int u,int v,int len,int val) { e[num].id=v;//存下一个点,以边找点 e[num].val=val; e[num].len=len; e[num].next=first[u]; first[u]=num; num++; } int main() { node cur; int ans=inf; int k,n,r,s,d,l,t; scanf("%d%d%d",&k,&n,&r); memset(first,-1,sizeof(first)); while(!q.empty()) q.pop(); num=0; for(int i=0;i<r;i++) { scanf("%d%d%d%d",&s,&d,&l,&t); add(s,d,l,t); } node1.id=1;//起始点 node1.len=0; node1.val=0; q.push(node1); while(!q.empty()) { cur=q.top(); q.pop(); if(cur.id==n) { ans=cur.len; break; } for(int i=first[cur.id];i!=-1;i=e[i].next) { if(k>=cur.val+e[i].val)//只有符合要求的点才会进入队列 { node1.id=e[i].id; node1.len=e[i].len+cur.len; node1.val=e[i].val+cur.val; q.push(node1); } } } if(ans==inf) printf("-1\n"); else printf("%d\n",ans); }
4、bellman(可以计算负权值)
那么先介绍一下负权回路,如果是单向图,那么所有权值之和为负数,这是负权回路。如果是无向图只要有一个负权值,就不会存在最短路,跟负权回路一个意思。
这个算法一般用于有负权值的最短路,但时间复杂度也不高。
有负权值的最短路
有了负权值dijs这种算法就不能用了,为什么呢?
因为这种算法是贪心的思想,每次松弛的的前提是用来松弛这条边的最短路肯定是最短的。然而有负权值的时候这个前提不能得到保证,所以dijs这种算法不能成立。
这里给出一个博客,看这个很清晰。
链接:http://blog.csdn.net/baidu_31818237/article/details/50611592
bellman的核心代码只有4行
for(int k=1; k<=n-1; k++) //进行n-1次松弛 for(int i=1; i<=m; i++) //枚举每一条边 if(dis[v[i]]>dis[u[i]]+w[i])//尝试松弛每一条边 dis[v[i]]=dis[u[i]]+w[i];这个算法也是遍历n-1遍找过所有的点,至于为什么是n-1呢。dijs算法n-1次遍历是因为有n-1个点需要遍历,这个也是因为最短路是一个不包含回路的路径,无论正负权回路都不能有,那么去掉回路,n个点任意两点之间就最多有n-1条边。但是程序可能在不到n-1次循环就已经找到了所有最短路,说明这个是最坏情况下是n-1次遍历。
dis同样是存在起始点到各个顶点的最短路,这个与dijs不同的是,dijs每次找到最近的点进行松弛操作,而这个bellman则是只要路程更短我就松弛。也是因为这样才能用来解决负权值问题。
那么怎么来看有负权值回路呢,如果有负权值回路,那最短路就不会存在,因为最短路会越来与小。那么在n-1轮松弛后,要是还能松弛就代表有负权值回路。
下边是完整代码
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f int dis[100],u[100],v[100],w[100]; int main() { int n,m,flag1,flag2; while(~scanf("%d%d",&n,&m)) { for(int i=1; i<=m; i++) scanf("%d%d%d",&u[i],&v[i],&w[i]); for(int i=1; i<=n; i++) dis[i]=inf; dis[1]=0; for(int k=1; k<=n-1; k++) { flag1=0;//用于标记本轮松弛是否发生 for(int i=1; i<=m; i++) { if(dis[v[i]]>dis[u[i]]+w[i]) dis[v[i]]=dis[u[i]]+w[i]; flag1=1;//有更新 } if(flag1==0) break;//dis数组没有更新,即没有松弛,结束算法 } flag2=0; for(int i=1; i<=m; i++) if(dis[v[i]]>dis[u[i]]+w[i]) flag2=1; if(flag2==1) printf("有负权回路\n"); else { for(int i=1; i<=n; i++) printf("%d ",dis[i]); } printf("\n"); } return 0; }
其实这个算法可以在优化,因为有可能在n-1轮松弛还没结束就已经是最短路,那么用队列进行优化。
4、spfa(队列优化的bellman)
我没找到两者有什么区别,本来spfa就是队列优化而来的bellman, 我用了邻接表,反正是要优化就优化到底,下边还会给出优先队列的spfa
代码
数组模拟邻接表
#include<stdio.h> #include<string.h> #include<queue> #include<algorithm> using namespace std; int dis[100],u[100],v[100],w[100],vis[100],first[100],next[100],c[100]; int flag; int spfa(int s,int n) { queue<int>q; memset(dis,0x3f,sizeof(dis)); dis[s]=0; memset(vis,0,sizeof(vis)); memset(c,0,sizeof(c)); q.push(s); vis[s]=1; flag=0; while(!q.empty()) { int x; x=q.front(); q.pop(); vis[x]=0; //队头元素出队,并且消除标记 for(int k=first[x]; k!=0; k=next[k]) //遍历顶点x的邻接表 { int y=v[k]; if(dis[x]+w[k]<dis[y]) { dis[y]=dis[x]+w[k]; //松弛 if(!vis[y]) //顶点y不在队内 { vis[y]=1; //标记 c[y]++; //统计次数 q.push(y); //入队 if(c[y]>n) //超过入队次数上限,说明有负环 return flag=0; } } } } } int main() { int n,m; while(~scanf("%d%d",&n,&m)) { memset(first,-1,sizeof(first)); for(int i=1;i<=m;i++) { scanf("%d%d%d",&u[i],&v[i],&w[i]); next[i]=first[u[i]]; first[u[i]]=i; } spfa(1,n); if(flag) printf("有负权回路\n"); else { for(int i=1;i<=n;i++) printf("%d ",dis[i]); } } }
这个是vector模拟邻接表
#include<stdio.h> #include<string.h> #include<algorithm> #include<map> #include<queue> #include<math.h> #include<vector> #include<iostream> #define inf 0x3f3f3f3f #define ll long long #define N 100000+10 using namespace std; int n,m; int x,y,z; bool v[100]; int d[100]; struct node { int y,z; }; vector<node>e[100]; void spfa(int x) { memset(v,0,sizeof(v)); d[x]=0; queue<int>q; q.push(x); v[x]=1; while(!q.empty()) { int st=q.front(); q.pop(); v[st]=0; for(int i=0;i<e[st].size();i++) { if(d[st]+e[st][i].z<d[e[st][i].y]) { d[e[st][i].y]=d[st]+e[st][i].z; if(!v[e[st][i].y]) { q.push(e[st][i].y); v[e[st][i].y]=1; } } } } } int main() { scanf("%d%d",&n,&m); memset(d,inf,sizeof(d)); for(int i=1;i<=m;i++) { scanf("%d%d%d",&x,&y,&z); e[x].push_back((node){y,z}); e[y].push_back((node){x,z}); //存入无向图 } spfa(1); for(int i=1;i<=n;i++) { printf("%d ",d[i]); } }
最后着重说一下邻接表可以用数组也可以用vector
实现
e[]这个数组,e[i][j]i为起点,j为终止点的标号,e[i][j].y就是终止点
例如
j:0 1 2 j只是标号第几个
i:1 (2,3 3,2 4,4) 括号内的东西就是终止点和权值,e[1][0].y=2(终止点),e[1][0].z=3(权值)
i:2 (1,2 3,5 4,6)
i:3 (1,2 2,4 4,5)
这样也可以实现链表,并且可以存无向图,数组实现的邻接表存无向图很麻烦
转自:https://blog.csdn.net/zezzezzez/article/details/70245548