Dijkstra算法
时间复杂度O(N2)。
单源最短路径算法,边权非负,基于贪心思想。
算法描述:
设起点为s,dis[v]表示从s到v的最短路径,pre[v]为v的前驱节点,用来输出路径。
(a)初始化:dis[v]=0x7fffffff;dis[v]=0;pre[s]=0;
(b)for(int i=1;i<=n;i++)
1.在未被访问过的点中找一个点u使得dis[u]是最小的。
2.u标记为已确定最短路径。
3.for与u相连的每个未确定最短路径的点v
if(dis[u]+w[u][v]<dis[v]){
dis[v]=dis[u]+w[u][v];//松弛
pre[v]=u;//记录这条路径
}
模板题:最小花费
code:
#include<iostream> #include<cstdio> #include<cstdlib> #define maxn 2010 using namespace std; int n,m,x,y,a,b,peo;//n m位总人数和可转账人对数,x y z a b见题目,peo指现在的这个人 double minn; double dis[maxn],z[maxn][maxn]; bool f[maxn]; void dijkstra(int d){ for(int i=1;i<=n;i++) dis[i]=z[d][i]; dis[d]=1;//******** f[d]=true;//******** for(int i=1;i<=n-1;i++){ minn=0.0; for(int j=1;j<=n;j++) if((!f[j])&&dis[j]>minn){ peo=j; minn=dis[j]; } f[peo]=true; if(peo==b) break; for(int j=1;j<=n;j++) if((!f[j])&&dis[peo]*z[peo][j]>dis[j]) dis[j]=dis[peo]*z[peo][j]; } } int main(){ cin>>n>>m; for(int i=1;i<=m;i++){ cin>>x>>y; cin>>z[x][y]; z[x][y]=(100-z[x][y])/100;//z[x][y]表示x和y转账扣完手续费后能留下的钱 z[y][x]=z[x][y]; } cin>>a>>b; dijkstra(a); printf("%.8lf\n",100.0/dis[b]); return 0; }View Code
Bellman—Ford算法
时间复杂度O(NE),其中N是顶点数,E是边数。
单源最短路径算法,能处理存在负边权的情况,但无法处理存在负权回路的情况,基于迭代思想。
算法描述:
设s为起点,dis[v]为s到v的最短距离,pre[v]为v的前驱,w[j]为边j的长度,且j连接u和v。
(a)初始化:dis[v]=0x7fffffff;dis[s]=0;pre[s]=0;
(b)for(int i=1;i<=n-1;i++)
for(int j=1;j<=E;j++)
if(dis[u]+w[j]<dis[v]){
dis[v]=dis[u]+w[j];
pre[v]=u;
}
SPFA算法
时间复杂度O(kE),其中k是一个较小的常数(所有顶点进队的频率次数),E为边数。注意,在特殊构造的图上,该算法很可能退化为O(nm)。
单源最短路径算法,能处理存在负边权的情况,但无法处理存在负权回路的情况,是队列优化的Bellman—Ford。
算法描述:
1 void spfa(){ 2 memset(dis,0x3f,sizeof(dis)); 3 memset(vis,0,sizeof(vis)); 4 dis[1]=0; 5 vis[1]=true; 6 q.push(1); 7 while(q.size()){ 8 int x=q.front(); 9 q.pop(); 10 vis[x]=false;//取出队头 11 for(int i=head[x];i;i=Next[i]){//扫描所有出边 12 int y=ver[i],z=edge[i]; 13 if(dis[y]>dis[x]+z){ 14 d[y]=d[x]+z; 15 if(!vis[y]) q.push(y),vis[y]=true; 16 } 17 } 18 } 19 }
判断有无负环:
方法一:判断点的入队次数,若某点入队次数超过N次则存在负环。
方法二:从起点到该点的最短路中的点数大于N。
Floyed—Warshall算法
时间复杂度O(N3)。
多源最短路径算法,能处理存在负边权的情况,基于DP思想。
算法描述:
(a)初始化:点u和v如果有边相连,则dis[u][v]=w[u][v]。
(b)for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if((i!=j)&&(k!=j)&&(i!=k)&&(dis[i][k]+dis[k][j]<dis[i][j]))
dis[i][j]=dis[i][k]+dis[k][j];
注意:k一定要放在最外层循环!!!很重要!!!
原理:k一次性更新了所有的点对,更新i和j时保证了i-k的距离已经被更新过了,所以f[k][i][j]表示i和j之间可通过前k个节点的最短路径。
精髓:依次扫描每一点(k),并以该点作为中介点,计算出通过k点的其他任意两点(i,j)的最短距离。
两种可能:1.经过第k个点不能找到最短路径,f[k][i][j]=f[k-1][i][j]
2.经过第k个点更找到更短的路径,f[k][i][j]=f[k-1][i][k]+f[k-1][k][j]
因为f[k]只与f[k-1]有关,所以f最外层一维空间可以省略。
Floyed找最小环:
1 for(int k=1;k<=n;k++){ 2 for(int i=1;i<=k-1;i++) 3 for(int j=i+1;j<=k-1;j++) 4 answer=min(answer,dis[i][j]+g[i][k]+g[k][j]);//找最小环 5 for(int i=1;i<=n;i++) 6 for(int j=1;j<=n;j++) 7 dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//找最短路径 8 }
*部分内容参考《信息学奥赛一本通》《算法竞赛进阶指南》,侵删致歉。