最短路径算法

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

算法描述:

 

 void spfa(){
     memset(dis,0x3f,sizeof(dis));
     memset(vis,0,sizeof(vis));
     dis[1]=0;
     vis[1]=true;
     q.push(1);
     while(q.size()){
         int x=q.front();
         q.pop();
         vis[x]=false;//取出队头
         for(int i=head[x];i;i=Next[i]){//扫描所有出边 
             int y=ver[i],z=edge[i];
             if(dis[y]>dis[x]+z){
                 d[y]=d[x]+z;
                 if(!vis[y]) q.push(y),vis[y]=true;
             }
         } 
     }
 }

 

 

 

判断有无负环:

方法一:判断点的入队次数,若某点入队次数超过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找最小环:

 

 for(int k=1;k<=n;k++){
     for(int i=1;i<=k-1;i++)
         for(int j=i+1;j<=k-1;j++)
             answer=min(answer,dis[i][j]+g[i][k]+g[k][j]);//找最小环 
     for(int i=1;i<=n;i++)
         for(int j=1;j<=n;j++)
             dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//找最短路径 
 }

 

 

 

*参考:《信息学奥赛一本通》《算法竞赛进阶指南》。

 

posted @ 2021-02-02 08:12  DReamLion  阅读(115)  评论(0编辑  收藏  举报