题意:n个点m条边,然后输入m条无向边和每条边的权值。问1 ~ n的最小路径。
Floyd
思路:来源:传送门
1.邻接矩阵g储存路径,同时最终状态代表点点的最短路径。如果没有直接相连的两点那么默认为一个很大的值(不要溢出)!,。
2.遍历从到,作为中继点依次加入图中。每个点加入进行试探是否有路径长度被更改。
3.而上述试探具体方法为遍历图中每一个点(双重循环),判断每一个点对距离是否因为加入的点而发生最小距离变化。如果发生改变,那么两点距离就更改。
4.重复上述直到最后插点试探完成。
5.k必须在最外层,否则不满足dp的无后效性:
采用动态规划思想,表示是i经过编号为的节点到j的最短路径
初值为原图的邻接矩阵
则可由转移得到,表示到的最短路径不经过k这个点
但也可以从和转移得到,表示到的最短路径经过k这个点
状态转移方程即
然后就会发现f最外层的一维空间可以省略,因为只与有关,所以算法实现时,要放在最外层。
6.举例说明:
加入第一个节点1,大家可以发现,由于1的加入,使得本来不连通的2,3点对和2,4点对变得联通,并且加入1后距离为当前最小。(可以很直观加入5之后2,4更短但是还没加入)。为了更好的描述其实此时的直接联通点多了两条:(2,3)和(2,4)。我们在dp中不管这个结果是通过前面那些步骤来的,但是在这个状态,这两点的最短距离就算它!
同时你可以发现加入1其中也使得3,1,4这样联通,但是 3,1,4联通的话距离为9远远大于本来的(3,4)为2,所以不进行更新。
当然这些红色的连线都是代表当前的最短路径。 这也和我们的需求贴合,我们最终要的是所有节点的最短路径。每个节点最终都应该有6条指向不同节点的边! 表示邻接矩阵的最终结果。
code:
#include<bits/stdc++.h> using namespace std; const int inf =1e6; const int num=105; int graph[num][num]; int n,m; void floyd() { int s=1; for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) if(graph[i][k]!=inf) for(int j=1;j<=n;++j) if(graph[i][j]>graph[i][k]+graph[k][j]) graph[i][j]=graph[i][k]+graph[k][j];//min会比if慢,poj 3259会卡掉min printf("%d\n",graph[s][n]); } int main() { while(~scanf("%d%d",&n,&m)) { if(!n&&!m) return 0; for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) graph[i][j]=inf; while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); graph[a][b]=graph[b][a]=c; } floyd(); } return 0; } /* 6 8 1 2 2 1 3 3 1 4 6 3 4 2 4 5 1 4 6 3 5 2 4 6 2 6 打印路径: #include<bits/stdc++.h> using namespace std; const int inf =1e6; const int num=105; int graph[num][num],g[num][num]; int n,m; void floyd() { int s=1; for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) if(graph[i][k]!=inf) for(int j=1;j<=n;++j) if(graph[i][j]>graph[i][k]+graph[k][j]) graph[i][j]=graph[i][k]+graph[k][j];//min会比if慢,poj 3259会卡掉min printf("%d\n",graph[s][n]); } void print(int a,int b) { if(graph[a][b]==g[a][b]) printf("%d->",a); for(int k=1;k<=n;++k) if(graph[a][b]==graph[a][k]+graph[k][b]) { print(a,k); print(k,b); break; } } int main() { while(~scanf("%d%d",&n,&m)) { if(!n&&!m) return 0; for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) graph[i][j]=inf,g[i][j]=0; while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); g[a][b]=g[b][a]=c; graph[a][b]=graph[b][a]=c; } floyd(); print(1,n); printf("%d\n",n); } return 0; } */
SPFA
其实是用队列优化Bellman-Foed算法。SPFA很像BFS。
参考博客:传送门
#include<bits/stdc++.h> using namespace std; const int inf=INT_MAX/10,maxn=10005; int to[maxn<<1],Next[maxn<<1],w[maxn<<1],head[maxn],tot; int n,m,cnt; int dis[maxn];//记录所有结点到起点的最短距离 bool inq[maxn];//inq[i]=true;表示结点i在队列中 int Neg[maxn];//判断负圈 int pre[maxn];//记录前驱结点 void print(int s,int t) { if(s==t) {printf("%d\n",s); return;} print(s,pre[t]); printf("%d ",t); }//打印从起点s到t的最短路径 void add(int u,int v,int val) { to[++tot]=v; Next[tot]=head[u]; w[tot]=val; head[u]=tot; } bool spfa(int s) { fill(Neg,Neg+maxn,0); Neg[s]=1; for(int i=1;i<=n;++i) {dis[i]=inf; inq[i]=false;} dis[s]=0; queue<int>q; q.push(s); inq[s]=true; while(!q.empty()) { int u=q.front(); q.pop(); inq[u]=false; for(int i=head[u];i;i=Next[i]) { int v=to[i],val=w[i]; if(dis[u]+val<dis[v]) { dis[v]=dis[u]+val; pre[v]=u; if(!inq[v]) { inq[v]=true; q.push(v); ++Neg[v]; if(Neg[v]>n) return true; } } } } printf("%d\n",dis[n]); //print(s,t); return false; } int main() { while(~scanf("%d%d",&n,&m)){ fill(head,head+maxn,0); tot=0; if(!n) return 0; while(m--) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } spfa(1); } return 0; }
Dijkstra
来源:传送门
在单源正权值最短路径,我们会用Dijkstra算法来求最短路径,并且算法的思想很简单——贪心算法:每次确定最短路径的一个点然后维护(更新)这个点周围点的距离加入预选队列,等待下一次的抛出确定。但是虽然思想很简单,实现起来是非常复杂的,我们需要前向星储存长度,需要优先队列(或者每次都比较)维护一个预选点的集合。还要用一个vis数组标记是否已经确定、还要---------
总之,Dijkstra算法的思想上是很容易接受的,但是实现上其实是非常麻烦的。但是单源最短路径没有更好的办法。复杂度也为O(n2)
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; struct edge{ int to,w; edge(int a,int b) {to=a;w=b;} }; vector<edge>e[105]; struct node{ int id,dis; node(int a,int b){id=a;dis=b;} bool operator<(const node&a ) const {return dis>a.dis;} }; int n,m; int pre[105]; //记录前驱结点 void print(int s,int t) { if(s==t) {printf("%d ",s); return;}//打印起点 print(s,pre[t]);//先打印前一个点 printf("%d ",t);//后打印当前点,最后打印的是终点t }//打印从s到t的最短路径 void dijkstra() { int s=1; //起点是1 int dis[105];//记录所有结点到起点的距离 bool done[105];//done[i]=true表示到结点i的最短路径已经找到 for(int i=1;i<=n;++i) { dis[i]=inf; done[i]=false; } dis[s]=0; //起点到自己的距离是0 priority_queue<node>q;//优先队列,存结点的信息 q.push(node(s,dis[s]));//起点进栈 while(!q.empty()) { node u=q.top(); q.pop(); if(done[u.id]) continue;//丢弃已经找到最短路径的结点,即集合A中的结点 done[u.id]=true; for(int i=0;i<e[u.id].size();++i) { edge y=e[u.id][i]; //u.id的第i个邻居是y.to if(done[y.to]) continue;//丢弃已经找到最短路径的邻居结点 if(dis[y.to]>y.w+u.dis) { dis[y.to]=y.w+u.dis; q.push(node(y.to,dis[y.to]));//扩展新的邻居,放到优先队列中 pre[y.to]=u.id;//如有需要,打印路径 } }//检查结点u的所有邻居 } printf("%d\n",dis[n]); //print_path(s,n); } int main() { while(~scanf("%d%d",&n,&m)) { if(!n&&!m) return 0; for(int i=1;i<=n;++i) e[i].clear(); while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); e[a].push_back(edge(b,c)); e[b].push_back(edge(a,c)); } dijkstra(); } }