zoukankan      html  css  js  c++  java
  • 最短路问题-图论

    最短路问题

    最短路问题是图论的经典问题,有以下常用算法:

    • Dijkstra算法/迪杰斯特拉算法 适用于正权图的单源最短路径
    • Bellman-Ford算法 含负权边的带权有向图的单源最短路问题。不能处理带负权边的无向图
    • SPFA算法 使用队列优化的Bellman-Ford算法
    • Floyd算法 求出图中每两点之间的最短路
    • A*算法 启发式的路径搜索算法
      这里先从最简单的Dijkstra算法讲起

    一. Dijkstra算法

    1.1 基本思想与算法

    Dijkstra可用于正权图上的单源最短路径,同时适用于有向图和无向图
    算法将顶点集V分成两部分:已找到最短路的顶点集合S,还未找到最短路的集合V-S
    伪代码如下

    清除所有点的标号(vis[]置零/集合S置空)
    初始化源点到每个顶点的距离d[]
    循环n次{
      在所有未标号节点中(在集合V-S中),选出d值最小的顶点p
      将p加入集合S
      对于从p出发的所有边(p,j),更新d[j]=min{d[j],d[p]+w(p,j)}
    }//每一轮将一个新顶点p加入集合S
    

    c++语言代码

      memset(vis,0,sizeof(vis));
      for(int i=0;i<n;i++) d[i]=(i==start?0:INF);
      for(int i=0;i<n;i++){
        int p,min=INF;
        for(int j=0;j<n;j++){
          if(!vis[j]&&d[j]<min){
            p=j;
            min=d[j];
          }
        }
        vis[p]=1;
        for(int j=0;j<n;j++){
          d[j]=min(d[j],d[p]+w(p,j));
          path[j]=p;//记录节点j的父节点,可以打印出最短路径内容
        }
      }
    

    可以看到上述代码的时间复杂度为(O(n^2))
    Dijkstra算法可以有多种推广,例如:

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<stack>
    
    const int maxn=1e3;
    const int INF=1e8;
    int n,m,st,en,x,y,z;//st-start,en-end
    /*
    val-每个点救援队数量
    valsum-到点i之前的数量之和
    path-点i前那个点的编号(即最短路径上的父节点编号)
    pathsum-到点i的最短路径的条数
    */
    int val[maxn],valsum[maxn],path[maxn],pathsum[maxn];
    /*
    d-该点到start的距离
    vis-是否加入S集合
    G-邻接矩阵
    */
    int d[maxn],vis[maxn],G[maxn][maxn];
    
    //建图初始化
    void init();
    //迪杰斯特拉算法
    void dijkstra();
    //输出路径
    void print();
    
    int main(){
        scanf("%d%d%d%d",&n,&m,&st,&en);
        for(int i=0;i<n;i++) scanf("%d",&val[i]);
        init();
        for(int i=0;i<m;i++){//考虑到是无向图
            scanf("%d%d%d",&x,&y,&z);
            G[x][y]=z;
            G[y][x]=z;
        }
        dijkstra();
        printf("%d %d
    ",pathsum[en],valsum[en]);
        //print();
    }
    
    void init(){
        memset(path,0,sizeof(path));
        memset(vis,0,sizeof(vis));
        memset(pathsum,0,sizeof(pathsum));
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(i==j) G[i][j]=0;
                else G[i][j]=INF;
            }
            valsum[i]=val[i];
        }
    }
    
    void dijkstra(){
        for(int i=0;i<n;i++){
            d[i]=G[st][i];//初始化源点到每个顶点的距离
        }
        d[st]=0;
        pathsum[st]=1;//初始化起点的最短路的条数是1
        int min1,p;
        for(int i=0;i<n;i++){
            min1=INF;
            for(int j=0;j<n;j++){
                if(!vis[j]&&d[j]<min1){
                    min1=d[j];
                    p=j;
                }
            }//找到V-S集合中最小的d[p]
            vis[p]=1;//将顶点p加入S集合中
            //更新与顶点p邻接的顶点的最短路距离
            for(int j=0;j<n;j++){
                if(!vis[j]&&d[j]>d[p]+G[p][j]){
                    d[j]=d[p]+G[p][j];
                    pathsum[j]=pathsum[p];//当松弛时,到j和到p的最短路条数相同
                    valsum[j]=valsum[p]+val[j];//松弛时,直接加上更新后的点值
                    path[j]=p;//记录父节点
                }
                else if(!vis[j]&&d[j]==d[p]+G[p][j]){
                    pathsum[j]+=pathsum[p];
                    if(valsum[j]<valsum[p]+val[j]){
                        valsum[j]=valsum[p]+val[j];
                        path[j]=p;
                    }//当都是最短路时,记录经过救援队最多的路径
                }
            }
        }
    }
    
    void print(){
        int tmp;
        std::stack<int> stk;
        stk.push(en);
        while(st!=en){
            tmp=path[en];
            stk.push(tmp);
            en=tmp;
        }
        while(!stk.empty()){
            printf("%d ",stk.top());
            stk.pop();
        }
        printf("
    ");
    }
    
    1.2复杂度优化

    上述算法复杂度为(O(n^2)),在最坏情况下(m)(n^2)是同阶的,但是对于稀疏图而言有(mll n^2)成立,因此可将时间复杂度由 (O(n^2)) 优化至(O(mlog n))
    主要的优化点在于"在所有未标号节点中(在集合V-S中),选出d值最小的顶点p",这一步可以维护一个小根堆来代替遍历操作寻找d值最小的顶点p

    struct HeapNode{
      int d,u;//d-顶点离start的距离,u-顶点的序号
      bool oprator < (const HeapNode& rhs) const{
        return d>rhs.d;
      }//priority_queue默认是优先级最大的元素在堆顶
    }
    
    void dijkstra(int start){
      priority_queue<HeapNode> Q;
      for(int i=0;i<n;i++) d[i]=INF;
      d[start]=0;
      memset(vis,0,sizeof(vis));
      Q.push((HeapNode){0,start});
      while(!Q.empty()){
        HeapNode x=Q.top();
        Q.pop();
        int u=x.u;
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=0;i<G[u].size;i++){
          Edge& e=edges[G[u][i]];
          if(d[e.to]>d[u]+e.dist){
            d[e.to]=d[u]+e.dist;
            path[e.to]=u;
            Q.push((HeapNode){d[e.to],e.to});
          }
        }
      }
    }
    
  • 相关阅读:
    servletContext
    解决Response输出时乱码
    servletConfig
    服务器和浏览器交互过程
    myeclipse配置
    servlet
    http协议
    配置虚拟主机
    配置主页
    开网站步骤
  • 原文地址:https://www.cnblogs.com/cbw052/p/10709445.html
Copyright © 2011-2022 走看看