mastertravels77 发表于 2022-1-24 09:16

算法学习笔记之图论:最短路问题

多源最短路

Dijkstra

在Dijkstra算法中,我们将节点分为「已经求得最短路的点」和「还未求得最短路的点」两个集合。算法须求得所有点的最短路,故需循环n次。 在每次循环中从「还未求得最短路的点」中,找出其中距离源点最近的一个点,用这个点更新源点到其它所有点的距离。算法复杂读为O(n^2);
核心代码如下
int dijkstra(){
    dist = 0;

    for(int i = 0;i < n;i++){
      auto t = -1;
      for(int j = 1;j <= n;j++){
            if(!st && (t == -1 || dist > dist))
                t = j;
      }

      st = true;

      for(int j = 1;j <= n;j++){
            dist = min(dist,dist + g);
      }
    }

    if(dist == INF)return INF;
    else return dist;
}
注:
在最后用t更新其它节点时,那些比t先求得最短路的点其实不会被更新。
Dijkstra算法无法处理含有副权边的情况,原因见下。
考虑反例:
1      1       1
1───────2───────3────── 5
│                  ▲
│                  │
│                  │
└───────────4─────┘
      3            -2根据Dijkstra算法,求得的路径为1->3->5,路径长度为3,但如果走1->4->3->5,长度为2,所以在存在副权边时,Dijkstra算法无法求得正确解。
主要是因为Dijkstra算法每次从「还未求得最短路的点」(即st为false)的点中选取距离源点最近的点(即dist最小)。
初始化阶段所有点的dist最初始化为正无穷,dist被初始化为0;
在第一次循环中,会取t==1,更新过后,dist=0,dist=1,dist=3
在第二次循环中,会取t==2,更新过后,dist=0,dist=1,dist=2,dist=3
在第三次循环中,会取t==3,更新过后,dist=0,dist=1,dist=2,dist=3,dist=3
在第四次循环中,会取t==4,更新过后,dist=0,dist=1,dist=1,dist=3,dist=3
在第五次循环中,会取t==5,更新过后,dist=0,dist=1,dist=2,dist=3,dist=3
堆优化版Dijkstra

void dijkstra(){
    priority_queue<PII,vector<PII>,greater<PII>> que;
    que.push({0,1});
    dist = 0;

    while(!que.empty()){
      auto curr = que.top();
      que.pop();

      auto t = curr.second;
      auto dis = curr.first;      

      if(st)continue;
      else st = true;


      for(int i = h;i != -1;i = ne){
            auto j = e;
            if(dist > dist + w){
                dist = dist + w;
                que.push({dist,j});
            }
      }
    }
    if(dist == 0x3f3f3f3f)cout << -1;
    else cout << dist;
}
插入数据修改小根堆的时间复杂度为 O(logn),总的复杂度为O(mnlogn),稀疏图m可以看作n,最后的时间复杂度为O(nlog)。 当图是绸密图时,m比大n x阶,复杂度为(m^x nlogn),显然不如前者。
Bellmen_ford算法

bellman-ford算法对所有边进行(n-1)松弛操作,只要算法中没有负权回路,bellman-ford算法就能求出最短路。复杂度为O(nm)
该算法与dijkstra算法的思路非常相象,不同之处在于一个对点操作,一个对边操作。
void bellman_ford(){
    memset(dist,0x3f,sizeof dist);
    dist = 0;

    for(int i = 0;i < n - 1;i++){
      memcpy(last,dist,sizeof dist);
      for(int j = 0;j < m;j++){
            auto e = edges;
            dist = min(dist,last + e.c);
      }
    }
}
SPFA

仔细看一看bellman-ford算法我们就会发现,在循环当中,有很多边根本不会被更新,只有与一条边相邻的边被更新之后,这条边才会被更新,这提醒我们可以使用queue对算法进行优化。
int spfa(){
    queue<int> que;
    que.push(1);
    dist = 0;
    st = true;

    while(!que.empty()){
      auto curr = que.front();
      que.pop();
      st = false;

      for(int i = h;i != -1;i = ne){
            auto j = e;
            if(dist > dist + w){
                dist = dist + w;
                if(!st){
                  que.push(j);
                  st = true;
                }
            }
      }
    }

    return dist;
}
一般情况下复杂度为O(m),不过可以构造出一些特殊的数据,使得最坏复杂度为O(nm)
多源最短路

Floyd算法

每次选一个点k做中间结点,对于两点,尝试i-k-j时候比i-j要小。
void floyd(){
    for(int k = 1;k <= n;k++){
      for(int i = 1;i <= n;i++){
            for(int j = 1;j <= n;j++){
                d = min(d,d+d);
            }
      }
    }
}
Reference:
页: [1]
查看完整版本: 算法学习笔记之图论:最短路问题