找回密码
 立即注册
查看: 269|回复: 0

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

[复制链接]
发表于 2022-1-24 09:16 | 显示全部楼层 |阅读模式
多源最短路

Dijkstra

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

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

        st[t] = true;

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

    if(dist[n] == INF)return INF;
    else return dist[n];
}
注:
在最后用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[1]被初始化为0;
在第一次循环中,会取t==1,更新过后,dist[1]=0,dist[2]=1,dist[4]=3
在第二次循环中,会取t==2,更新过后,dist[1]=0,dist[2]=1,dist[3]=2,dist[4]=3
在第三次循环中,会取t==3,更新过后,dist[1]=0,dist[2]=1,dist[3]=2,dist[4]=3,dist[5]=3
在第四次循环中,会取t==4,更新过后,dist[1]=0,dist[2]=1,dist[3]=1,dist[4]=3,dist[5]=3
在第五次循环中,会取t==5,更新过后,dist[1]=0,dist[2]=1,dist[3]=2,dist[4]=3,dist[5]=3
堆优化版Dijkstra

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

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

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

        if(st[t])continue;
        else st[t] = true;


        for(int i = h[t];i != -1;i = ne){
            auto j = e;
            if(dist[j] > dist[t] + w){
                dist[j] = dist[t] + w;
                que.push({dist[j],j});
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f)cout << -1;
    else cout << dist[n];
}
插入数据修改小根堆的时间复杂度为 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[1] = 0;

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

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

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

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

    return dist[n];
}
一般情况下复杂度为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[j] = min(d[j],d[k]+d[k][j]);
            }
        }
    }
}
Reference:
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 21:36 , Processed in 0.064066 second(s), 22 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表