1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<queue> 5 #include<cstring> 6 #define MAX 9999999 7 8 using namespace std; 9 10 int G[203][203];//二维数组图的存储 11 int n,s,t;//n点数 s起点 t终点 12 13 void dijkstra() 14 { 15 bool vis[203];//相当于集合Q的功能,标记该点是否访问过 16 int dis[203];///保存最短路径 17 int i,j,k; 18 19 for(i=0;i<n;i++) 20 dis[i]=G[s][i];//s->各个点的距离 21 memset(vis,false,sizeof(vis));//初始为假 ,表示未访问过 22 dis[s]=0;//s->s点距离 s点为起点 23 vis[s]=true;//s点访问过,标记为真 24 for(i=1;i<n;i++)//G.v-1次操作+上次s点的访问=G.v次操作 25 { 26 k=-1; 27 for(j=0;j<n;j++)//从尚未访问过的点中选一个距离最小的点 28 { 29 if(!vis[j]&&(k==-1||dis[k]>dis[j])) //该点未访问过&&距离最小的 30 k=j; 31 } 32 if(k==-1)//若图是不连通的则提前结束 33 break; 34 vis[k]=true;//将k点标记为访问过的 35 for(j=0;j<n;j++) //松弛操作 36 if(!vis[j]&&dis[j]>dis[k]+G[k][j]) //该点未访问&&可以松弛 37 dis[j]=dis[k]+G[k][j]; //j点的距离大于当前点的距离+w(k,j),则松弛成功,更新 38 39 } 40 printf("%d ",dis[t]==MAX?-1:dis[t]); 41 } 42 43 int main() 44 { 45 int m,i,j,u,v,w; 46 47 while(~scanf("%d%d",&n,&m)) 48 { 49 for(i=0;i<n;i++) 50 for(j=0;j<n;j++) 51 G[i][j]=i==j?0:MAX;//初始化,本身到本身为0,否则为MAX 52 while(m--) 53 { 54 scanf("%d%d%d",&u,&v,&w); 55 if(G[u][v]>w)//因为初始化操作 56 G[u][v]=G[v][u]=w;//无向图 双向 57 58 } 59 scanf("%d%d",&s,&t); 60 dijkstra(); 61 } 62 return 0; 63 }
eg:http://acm.hdu.edu.cn/showproblem.php?pid=1874
题意:多组输入。第一行给你两个数n(代表点),m(代表边)
第2—m+1行 ,每行三个数u,v, w。0<=u,v<n, w>=0;
第m+2行两个数 s, t 。 s为源点,t为要到达的目的点。
求s到t 的最短路,若存在最短路输出最短路的值,否则输出-1。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <iostream> 5 #include <queue> 6 #define MAX 9999999 7 8 using namespace std; 9 //pair 的first 保存的为最短距离, second保存的为顶点编号 10 typedef pair<int, int >P;//对组 不知道请自行百度 11 12 struct node 13 { 14 int v, w;//v 为到达的点, w为权重 15 int next;//记录下一个结构体的位置 ,就向链表的next功能是一样的 16 }; 17 node edge[2003];//存所有的边,因为是无向图,所以*2 18 int cnt;//结构体的下标 19 int n, s, t;//n 点数,s 起点,t止点 20 int head[203];//和链表的头指针数组是一样的。只不过此处head[u]记录的为最后加入 edge 的且与u相连的边在 edge 中的位置,即下标 21 22 void add(int u, int v, int w)//加边操作 23 { 24 edge[cnt].v = v; 25 edge[cnt].w = w; 26 edge[cnt].next = head[u];//获得下一个结构体的位置 27 head[u] = cnt++;//记录头指针的下标 28 } 29 30 void dijkstra() 31 { 32 int dis[203];//最短路径数组 33 int i, v;//v保存从队列中取出的数的第二个数 也就是顶点的编号 34 priority_queue<P,vector<P>,greater<P> >que;//优先队列 从小到大 35 node e;//保存边的信息,为了书写方便 36 P p;//保存从队列取出的数值 37 38 fill(dis,dis+n,MAX);//初始化,都为无穷大 39 dis[s] = 0;//s—>s 距离为0 40 que.push(P(0,s));//放入距离 为0 点为s 41 while(!que.empty()){ 42 p = que.top();//取出队列中最短距离最小的对组 43 que.pop();//删除 44 v = p.second;//获得最短距离最小的顶点编号 45 if(dis[v] < p.first)//若取出的不是最短距离 46 continue;//则进行下一次循环 47 for(i=head[v];i!=-1;i=edge[i].next)//对与此点相连的所有的点进行遍历 48 { 49 e = edge[i];//为了书写的方便。 50 if(dis[e.v]>dis[v]+e.w){//进行松弛 51 dis[e.v]=dis[v]+e.w;//松弛成功 52 que.push(P(dis[e.v],e.v));//讲找到的松弛成功的距离 和顶点放入队列 53 } 54 } 55 } 56 printf("%d ",dis[t]==MAX?-1:dis[t]);//输出结果 57 } 58 59 int main() 60 { 61 int m, u, v, w; 62 63 while(scanf("%d %d",&n,&m)==2){//获取点数 边数 64 cnt = 0;//结构体下标从0开始 65 memset(head,-1,sizeof(head));//初始化head[N]数组 66 while(m--){ 67 scanf("%d %d %d",&u,&v,&w);//获取u,v,w(u,v) 68 add(u,v,w);//加边 69 add(v,u,w);//加边 70 } 71 scanf("%d %d",&s,&t);//获取起止点 72 dijkstra(); 73 } 74 return 0; 75 }
Dijkstra算法在运行过程中维持的关键信息是一组结点集合S。从源结点s 到该集合中每个结点之间的最短路径都已经被找到。算法重复从结点集V-S中选择最短路径估计最小的结点u,讲u加入到 集合S,然后对所有从u发出的边进行松弛。
Dijkstra 算法如下://这个描述使用的最小优先队列Q来保存结点集合,每个结点的关键值为其d值。
1 对图的建立和处理,dis[N]数组的初始化等等操作
2 S = ∅
3 Q = G.V
4 while Q ≠ ∅
5 u = EXTRACT-MIN(Q)
6 S = S ∪ {u}
7 for each vertex v∈ G.Adj[u]
8 relax(u,v,w)
此算法在此分为二步 : 第二大步中又分为3小步
1) 第1~3行 对dis[N]数组等的初始化,集合S 为∅,Q集合为G.V操作
2) 第4~8行 ① 第4行 进行G.V次操作
② 第5~行 从Q中找到一个点,这个点是Q中所有的点 s—>某点 最小的最短路径的点,并将此点加入S集合
③ 第7~8行 进行松弛操作,用此点来更新其他路径的距离。
该算法适合于权值不为负数的有向图的单源最短路径。
why?
because :
0 3 4
3 0 -2
4 -2 0
求d{1,2} 根据dirkstra实际就是贪心算法,每次找到都是最短路径, d{1,2} =3,但是因为有负权,d{1,2}=2;
so 命题不成立#
1 #include<iostream> 2 #include<cstdio> 3 #include<iostream> 4 #include<queue> 5 #define MAX 99999999 6 using namespace std; 7 8 9 struct node 10 { 11 int u,v,w; 12 }; ///边的数值 13 14 node edge[2003]; 15 int n,m,s,t; 16 17 void bellman_ford() 18 { 19 int i,j; 20 bool flag;//用于优化的 21 int dis[203];//保存最短路径 22 //初始化 23 fill(dis,dis+n,MAX); 24 dis[s]=0;//源点初始化为0 25 m=m<<1; 26 for(i=1;i<n;i++) 27 { 28 flag=false;//刚刚标记为假 29 for(j=0;j<m;j++)//】 30 { 31 if(dis[edge[j].u]>dis[edge[j].v]+edge[j].w){ 32 dis[edge[j].u]=dis[edge[j].v]+edge[j].w; 33 flag=true;//若松弛成功则标记为真 34 } 35 } 36 if(!flag)//若所有边i的循环中没有松弛成功的 37 break;//推出循环 38 } 39 printf("%d ",dis[t]==MAX?-1:dis[t]); // 输出结果 40 41 } 42 43 int main() 44 { 45 int i; 46 while(~scanf("%d%d",&n,&m)) 47 { 48 for(i=0;i<m;i++) 49 { 50 scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w); 51 edge[i+m].u=edge[i].v;//因为无向图所以u->v和v->u一样的 52 edge[i+m].v=edge[i].u;//so 53 edge[i+m].w=edge[i].w;//so 54 } 55 scanf("%d%d",&s,&t); 56 bellman_ford(); 57 } 58 return 0; 59 }
Bellman-Ford算法 :
bellman-ford 算法解决的是一般情况下的单源最短路径问题,其边可以为负值。bellman-ford算法可以判断图是否存在负环,若存在负环会返回一个布尔值。当然在没有负环存在的 情况下会返回所求的最短路径的值。
bellman-ford() :算法如下
1 图的初始化等操作
2 for i = 1 to |G.V| - 1 // |G.V| 为图 G的点的总数
3 for each edge(u,v)∈G.E //G.E 为图 G 的边
4 relax(u,v,w) 也就是if v.d>u.d+w(u,v) , v.d = u.d+w(u,v);
5 for each edge(u,v)∈G.E
6 if v.d>u.d+w(u,v) //v.d为出发源点到结点v的最短路径的估计值 u.d亦如此 w(u,v) 为u结点到v结点的权重值(通俗点就是u—>v的花费)。
7 return false;
8 return true
此算法分为3步:
1) 第1行对图进行初始化,初始化dis[N] = +∞,dis[s] = 0;
2) 第2~4行为求最短路的过程,是对图的每一条边进行|V|-1次松弛操作,求取最短路径。
3) 第5~8行为对每条边进行|V|-1次松弛后,检查是否存在负环并返回相应的布尔值,因为进行|V|-1次松弛后若没有负环则v.d的值确定不变,若有负环则会继续进行松弛操作,因为一个数+负数是一定比它本身要小的。
此算法的 时间复杂度为O(VE)。
路径还原
1 int per[503]; 2 memset(per,-1,sizeof(per)); 3 for(i=0;i<n;i++) 4 { 5 dis[i]=G[s][i]; 6 per[i]=s; 7 } //初始化; 8 9 //k为找到当前最短路的点 10 if(!vis[j]&&dis[j]>dis[k]+G[k][j]) 11 { 12 dis[j]=dis[k]+G[k][j]; 13 per[j]=k;//当前前驱为k 14 } 15 16 //输出路径; 17 int p;//记录前驱 18 for(i=0;i<n;i++) 19 { 20 if(per[i]==-1)//没有前驱 21 printf("%d ",i); 22 else 23 { 24 p=per[i]; 25 while(p>0) 26 { 27 printf("%d ",p); 28 p=per[p]; 29 } 30 printf("0 "); 31 } 32 }