首页 > 其他分享 >基础图论 - 最短路

基础图论 - 最短路

时间:2023-02-03 00:12:15浏览次数:60  
标签:图论 dist idx int 短路 基础 st include

基础图论之最短路

 

朴素版的Dijkstra算法

AcWing - Dijkstra求最短路 I

  • 稠密图:数据范围 m~n^2  (m = 1e5, n = 500),复杂度n^2,邻接矩阵存图
  • 解题思路:外层迭代 n 次,每次确定一个点的最短路,st[i] 表明当前已确定最短距离的点,确定最短距离的点 t 之后,用 t 更新其他点的距离。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510;
int n, m;
int g[N][N]; //稠密图用邻接矩阵存,稀疏图用邻接表存
int dist[N];   //表示从1号点到该点的当前的最短路
bool st[N];     //判断该点的最短路是否已经确定

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for(int i = 0; i < n; i ++)   //迭代n次,每次确定一个点的最短路
    {
        int t = -1;
        for(int j = 1; j <= n; j ++)
            if(!st[j] && (t == -1 || dist[t] > dist[j]))  //如果当前的t不是最短的,更新为j
                t = j;
        st[t] = true;    //表明t已经完成最短路径
        for(int j = 1; j <= n; j ++)
            dist[j] = min(dist[j], dist[t] + g[t][j]); //用t更新到点j的最短路径,即一号点到t的距离+t到j的距离
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    cin >> n >> m;
    memset(g, 0x3f, sizeof g);  
    while(m --)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);  //该题会有重边,只需取边权最小的一条边
    }
    int t = dijkstra();
    cout << t << endl;
    return 0;
}

 

堆优化版的Dijkstra算法

AcWing - Dijkstra求最短路 II

  • 稀疏图:数据范围 m~n  (m = 1e5, n = 1e5),复杂度mlogn,邻接表存图
  • 解题思路:遍历每条边,如果dist[j] > distance + w[i], 则更新到点 j 的距离,队列维护点 j 以及从源点到点 j 的最短距离,遍历边的时间复杂度 m ,在优先队列中更新一个点的时间复杂度为 logn,总时间复杂度为 mlogn,st[i]含义同上。
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1e6+10;
typedef pair<int, int> PII;
int n, m;
int h[N], e[N], ne[N], w[N], idx;   //w[N]记录权重
int dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    priority_queue<PII, vector<PII>, greater<PII> >heap;
    heap.push({0, 1});     //前一个数表示距离,后一个数表示编号,此时将编号唯一的点加入队列

    while(heap.size())  
    {
        auto t = heap.top();   
        heap.pop();           

        int distance = t.first, ver = t.second;
        if(st[ver]) continue;     
        st[ver] = true;
        for(int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}


int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    while(m --)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    int t = dijkstra();
    cout << t;
    return 0;
}

 

Bellman_ford算法

AcWing - 有边数限制的最短路

  • 时间复杂度:O(mn) ,外层 k(k~n)个循环,确定从1号点到n号点的最多经过k条边的最短距离,内从每次遍历m条边。
  • Bellman_ford算法着重解决有边数限制的最短路。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, M = 100010;

int n, m, k;
int dist[N], backup[N];

struct Edge
{
    int a, b, w;
}edge[M];

int Bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    for(int i = 1; i <= k; i ++)           //遍历k次,从1号点到n号点的最多经过k条边的最短距离
    {
        memcpy(backup, dist, sizeof backup);
        //跟前几个不同的是,该题有边数限制,不能时时用前一个点的距离去更新后一个点的距离
        
        for(int j = 1; j <= m; j ++)
        {
            int a = edge[j].a, b = edge[j].b, w = edge[j].w;
            dist[b] = min(dist[b], backup[a] + w);
            //例:当j为1时,此时dist[2]=1,求dist[3]时,不能用dist[2]去更新到点3的距离,因为题中要求只经过一条边,此时只能用dist[1]去更新距离,
            //也即backup[1]+w,假如题中要求两条边,则会重新将dist[2]的值赋值给backup[2],此时再求dist[3]是就可用dist[2]更新,即backup[2]+w
        }
    }
    if(dist[n] > 0x3f3f3f3f / 2) cout << "impossible";
    else cout << dist[n];
}

int main()
{
    cin >> n >> m >> k;
    
    for(int i = 1; i <= m; i ++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        edge[i] = {a, b, w};
    }
    
    Bellman_ford();
    
    return 0;
}

 

SPFA算法

AcWing - spfa求最短路

AcWing - spfa判断负环

  • SPFA算法 是对 Bellman_ford 算法的优化,后者会遍历所有的边,因此会有很多无意义的更新,而我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新,该节点才会得到更新,因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
  • spfa可以判断负环,因为只要距离更新就会有元素入队,可以用一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n,就证明存在负环。
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int w[N], ne[N], h[N], e[N], idx;
bool st[N];
int dist[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;  //从队列中取出来之后该节点st被标记为false,代表之后该节点如果发生更新可再次入队

        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                //当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率
                if(!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) cout << "impossible";
    else cout << dist[n];
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while(m --)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    spfa();
    return 0;
}

 

Floyd算法

AcWing - Floyd求最短路

  • Floyd 算法着重解决多源最短距离,时间复杂度 O(n ^ 3)
#include<iostream>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, q;
int d[N][N];

void floyd()
{
    //从 i 到 j 经过点 k 的距离
    for(int k = 1; k <= n; k ++)
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++) 
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);   //从 i 到 k 的距离 + 从 k 到 j 的距离 
    //f[i, j, k] = min(f[i, j, k - 1], f[i, k, k - 1] + f[k, j, k - 1])
}
int main()
{
    cin >> n >> m >> q;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            if(i == j) d[i][j] = 0;     //解决自环
            else d[i][j] = INF;       
        }
    }
    while(m --)
    {
        int a, b, c;
        cin >> a >> b >> c;
        d[a][b] = min(d[a][b], c);
    }
    floyd();
    while(q --)
    {
        int a, b;
        cin >> a >> b;
        int t = d[a][b];
        if(d[a][b] > INF / 2) cout << "impossible" << endl;
        else cout << t << endl;
    }
    return 0;
}

 

注意事项和区别

https://www.acwing.com/file_system/file/content/whole/index/content/369161/

为什么Dijkstra不能使用在含负权的图中?

https://www.acwing.com/solution/content/6320/

标签:图论,dist,idx,int,短路,基础,st,include
From: https://www.cnblogs.com/breeze-ku/p/17087802.html

相关文章