• 图的最短路


    第一种 Dijkstra算法(堆优化)

    时间复杂度:无堆优化O(n^2),有堆优化O((m+n)logn)。
    思想:用已经求出来的有最小值的节点松弛它所连的其他节点,即每次查找剩下所有节点中最小的一个用它松弛其他边
    Dijkstra 每次循环都可以确定一个顶点的最短路径,故程序需要循环 n-1 次。
    堆优化:(适用于边数远少于n^2的稀疏图)查找时用小根堆push一个节点和此节点新权值,并记录此节点查过(不删除此堆中节点以前权值,所以占空间较大(*10)),每次取堆顶看是否用过 若未用过用它去松弛其他节点,反之则跳过直到堆为空结束。

    //无堆优化+邻接矩阵
    //输入n和m,代表n个节点,m条边,然后是m行输入,每行有x,y,z,代表x到y的路距离为z。
    //问题:从1出发到各点的最短路径。
    #include<cstdio>  
    #include<cstring>  
    const int maxn=100;  
    int map[maxn][maxn];  
    int dis[maxn];  
    int path[maxn];  
    bool vis[maxn];  
    int n;  
    void dijk(int s)  
    {  
        memset(path,-1,sizeof(path));  
        memset(dis,0x3f,sizeof(dis));      //快速初始化为无穷大  
        memset(vis,0,sizeof(vis));  
        dis[s]=0;  
        while(1)  
        {  
            int k=0;  
            for(int j=1;j<=n;j++)  
            {  
                if(!vis[j]&&dis[j]<dis[k]) //这一步找未收录顶点中dis值最小的  
                    k=j;  
            }  
            if(!k) return ;               //没有未收录的顶点,则返回(注意顶点从1开始,k为0一定是没找到)  
            vis[k]=1;                     //收录顶点k  
            for(int j=1;j<=n;j++)  
            {  
                if(dis[j]>dis[k]+map[k][j])  
                {  
                    dis[j]=dis[k]+map[k][j];  
                    path[j]=k;  
                }  
            }  
        }  
    }  
    void print(int x)  
    {  
        if(x==-1) return ;  
        print(path[x]);  
        printf("%d->",x);  
    }  
    int main()  
    {  
        int m,x,y,z,order;  
        scanf("%d%d",&n,&m);  
        memset(map,0x3f,sizeof(map));  
        for(int i=0;i<m;i++)  
        {  
            scanf("%d%d%d",&x,&y,&z);  
            map[x][y]=map[y][x]=z;  
        }  
        dijk(1);  
        scanf("%d",&order);  
        printf("%d
    ",dis[order]);  
        print(path[order]);  
        printf("%d",order);  
        return 0;  
    } 

    //eg:洛谷P3371
    //堆优化+邻接表
    #include<cstdio>
    #include<iostream>
    using namespace std;
    const int INF=2147483647;
    int n,m,st,cnt,minn=INF,siz;
    int head[10001],dis[10001],vis[100001];
    struct edge{
        int to,next,mes;
    }edge[500001];
    struct hp{
        int dis,nu;
    }hp[100001];
    
    void add(int x,int y,int z){
        edge[++cnt].next=head[x];
        edge[cnt].to=y;
        edge[cnt].mes=z;
        head[x]=cnt;
    }
    
    void push(int x,int y){
        siz++;
        hp[siz].nu=x,hp[siz].dis=y;
        int now=siz;
        while(now>1){
            if(dis[hp[now].nu]<dis[hp[now/2].nu]){
                swap(hp[now],hp[now/2]);
                now=now/2;
            }
            else break;
        }
    }
    
    int del(){
        int res=hp[1].nu;
        hp[1]=hp[siz--];
        int now=1;
        while(2*now<=siz){
            int tp=2*now;
            if(tp<siz&&dis[hp[tp].dis]>dis[hp[tp+1].nu]) tp++;
            if(dis[hp[now].nu]>dis[hp[tp].nu]){
                swap(hp[now],hp[tp]);
                now=tp;
            }
            else break;
        }
        return res;
    }
    
    void dij(int st){
        for(int i=1;i<=n;i++) dis[i]=INF;
        dis[st]=0;
        push(st,dis[st]);
        int now=st;
        while(siz){
            now=del();
            if(vis[now]) continue;
            vis[now]=false;
            for(int i=head[now];i;i=edge[i].next){
                int mu=edge[i].to;
                if(dis[mu]>dis[now]+edge[i].mes){
                    dis[mu]=dis[now]+edge[i].mes;
                    push(mu,dis[mu]);
                }
            }
        }
    }
    
    int main(){
        scanf("%d %d %d",&n,&m,&st);
        for(int i=1;i<=m;i++){
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            add(x,y,z);
        }
        dij(st);
        for(int i=1;i<=n;i++) printf("%d ",dis[i]);
        return 0;
    } 

    第二种 SPFA算法

    时间复杂度:O(KE),E为边数,K为常数
    最差时间复杂度:O(n^3),即O(nE)。
    特点:在 Bellman-ford 算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。,可以判断有无负权回路(若有,则不存在最短路)。
    而其检测负权回路的方法也很简单,如果某个点进入队列的次数大于等于 n,则存在负权回路,其中 n 为图的顶点数。

    //eg:洛谷P1828
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int INF=0x3f3f3f3f;
    int n,m,cnt,minn=INF,p,c;
    int que[4010],dis[801],hea[801],place[801];
    bool flag[801];
    struct edge{
        int to,next,mes;
    }edge[4010];
    
    void add_n(int x,int y,int z){
        edge[++cnt].next=hea[x];
        edge[cnt].to=y;
        edge[cnt].mes=z;
        hea[x]=cnt;
    }
    
    void spfa(int t){
        memset(flag,false,sizeof flag);
        memset(dis,INF,sizeof dis);
        int head=1,tail=1; 
        dis[t]=0;
        que[head]=t; 
        flag[t]=true;
        while(head<=tail){
            for(int i=hea[head[que]];i;i=edge[i].next){
                if(dis[edge[i].to]>dis[que[head]]+edge[i].mes){
                    dis[edge[i].to]=dis[que[head]]+edge[i].mes;
                    if(!flag[edge[i].to])
                        que[++tail]=edge[i].to;
                        flag[edge[i].to]=true;
                }
            }
            flag[que[head]]=false;
            head++;
        }
        int sum=0;
        for(int i=1;i<=n;i++) sum+=dis[place[i]];
        if(sum<minn) minn=sum;
    }
    
    int main()
    {
    //    freopen("testdata.txt","r",stdin);
        scanf("%d %d %d",&n,&p,&c);
        for(int i=1;i<=n;i++) 
            scanf("%d",&place[i]);
        for(int i=1;i<=c;i++){
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            add_n(x,y,z);
            add_n(y,x,z);
        }
        for(int i=1;i<=p;i++) 
            spfa(i);
        printf("%d",minn);
        return 0;
    }

    第三种: Floyd算法

    时间复杂度:O(n^3)
    思想:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:依次扫描每一点(k),并以该点作为中介点,计算出通过k点的其他任意两点(i,j)的最短距离
    步骤:
    1、用D[v][w]记录每一对顶点的最短距离。
    2、依次扫描每一个点,并以其为基点再遍历所有每一对顶点D[][]的值,看看是否可用过该基点让这对顶点间的距离更小。

    #include<stdio.h>  
    #include<iostream>  
    #include<limits.h>  
    #include<string.h>  
    #include<algorithm>  
    using namespace std;  
    const int INF=INT_MAX/100;  
    int d[3000][3000],n,m;  
    void floyed()  
    {  
        for(int k=0; k<n; k++)                                  //插入k点  
        {  
            for(int i=0; i<n; i++)  
            {  
                for(int j=0; j<n; j++)  
                {  
                    if(d[i][k]<INF&&d[k][j]<INF)                //消除加法溢出问题  
                        d[i][j]=min(d[i][j],d[i][k]+d[k][j]);   //更新两点距离  
                }  
            }  
        }  
    }  
    void init()  
    {  
        cin>>n>>m;  
        for(int i=0; i<n; i++)  
        {  
            for(int j=0; j<n; j++)  
                if(i==j)  
                    d[i][j]=0;  
                else  
                    d[i][j]=INF;  
        }  
        for(int i=0; i<m; i++)  
        {  
            int sta,stb,coc;  
            cin>>sta>>stb>>coc;  
            d[sta][stb]=coc;  
        }  
    }  
    int main()  
    {  
        init();  
        floyed();  
        return 0;  
    } 

    算法类比:

    dj和ford算法用于解决单源最短路径,而floyd算法解决多源最短路径。

    dj适用稠密图(邻接矩阵),因为稠密图问题与顶点关系密切;
    ford和dj+堆优化算法适用稀疏图(邻接表),因为稀疏图问题与边关系密切。
    floyd在稠密图(邻接矩阵)和稀疏图(邻接表)中都可以使用;

    PS:dj算法虽然一般用邻接矩阵实现,但也可以用邻接表实现,只不过比较繁琐。而ford算法只能用邻接表实现。

    dj算法不能解决含有负权边的图;
    而Floyd算法和Ford算法可以解决含有负权边的问题,但都要求没有总和小于0的负权环路(需要注意的是,如果是无向图,只要存在一条负边,该图就存在负权回路)。

    SPFA算法可以解决负权边的问题,而且复杂度比Ford低。形式上,它可以看做是dj算法和BFS算法的结合。

    3种算法都是既能处理无向图问题,也能处理有向图问题。因为无向图只是有向图的特例,它们只是在邻接矩阵或邻接表的初始化时有所区别。

    版权声明:本文为博主原创文章,未经博主允许不得转载。 博主:https://www.cnblogs.com/Menteur-Hxy/
  • 相关阅读:
    fork 开源项目后如何参与项目
    C51 头文件中的 extern
    windows常用命令
    boost(barrier)
    boost库(条件变量)
    线程间同步的几种方式(了解)
    stl 迭代器(了解)
    std::thread
    同步,异步,阻塞,非阻塞
    std::thread join和detach区别
  • 原文地址:https://www.cnblogs.com/Menteur-Hxy/p/9248059.html
Copyright © 2020-2023  润新知