最短路问题-图论

最短路问题

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

  • 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\n",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("\n");
}

猜你喜欢

转载自www.cnblogs.com/cbw052/p/10709445.html