最短路(dijkstra、堆优化dijkstra、bellman-ford、spfa、floyd)


在求最短路问题时,不区分有向图和无向图,有向图和无向图的问题都可以用有向图的算法解决,只是存图时有所不同

一、朴素dijkstra算法(基于贪心)(邻接矩阵)(单源最短路、边权为正)(适用于稠密图)(稠密图的m和n^2同级别,稀疏图的m和n同级别)

      时间复杂度O(n^2)

//重复n-1次,每次在剩下的未确定最短距离的点中,找距离源点最近的点,用这个距离来更新每个点到源点的距离
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )  //n-1次
    {
        int t = -1;  //每次找到当前第一个未确定最短距离的点j,用j更新t,以这个t为基准,继续寻找比它距离源点更小的点
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))   //更新条件
                t = j;  //最终找到当前未确定最短距离的点中距离源点最短的点

        for (int j = 1; j <= n; j ++ )  //用找到的点的距离更新所有点的距离(三角不等式)
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

 二、堆优化版的dijkstra(单源最短路、边权为正)

        时间复杂度O(mlogn)

#include 
#include 
#include 
#include 

using namespace std;

typedef pair<int, int> PII; //pair存点距离源点的距离和该节点的编号

const int N = 1e6 + 10;//这里是边的取值范围

int n, m;
int h[N], w[N], e[N], ne[N], idx;//数组模拟链表,头插法
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, greater> heap;//目的是用来找当前未确定最短距离且距离源点最近的点
    heap.push({0, 1});

    while (heap.size())
    {
        auto t = heap.top();//这里的t就是pair类型
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;//编号为ver的点在之前就已经考虑过了,不考虑冗余备份,跳过即可
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + 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);
    }

    cout << dijkstra() << endl;

    return 0;
}

三、bellman-ford算法(单源最短路、存在负权边) (可以判断是否存在负环,但时间复杂度太高,一般不用它来判断)

       时间复杂度O(nm)

//有边数限制的题目就只能用bellman-ford来做,不能用spfa了,例题如下:

#include 
#include 
#include 

using namespace std;

const int N = 510, M = 10010;

struct Edge
{
    int a, b, c;
}edges[M];//结构体存储边的信息

int n, m, k;
int dist[N];
int last[N];//备份

void bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);

    dist[1] = 0;
    for (int i = 0; i < k; i ++ )   //边数限制,不超过k条边
    {
        memcpy(last, dist, sizeof dist);//防止最后串联,若串联就没法保证边数限制了。因此每次迭代前备份一下,保证只用上一次迭代的结果,这样就不会串联了,从而保证在边数限制内
        //memcpy的效率很高

        for (int j = 0; j < m; j ++ )//遍历了所有边
        {
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], last[e.a] + e.c);
        }
    }
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);

    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        edges[i] = {a, b, c};
    }

    bellman_ford();

    if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");//存在负权边。比如说0x3f3f3f3f经过一条值为-2负权边后,后面的点是可以把前面的点更新的。所以这里是dist[n]>0x3f3f3f3f/2,而不是dist[n]==0x3f3f3f3f
    else printf("%d\n", dist[n]);

    return 0;
}

四、spfa算法(单源最短路、存在负权边) (求最短路、判断是否存在负环)(dijkstra可以过的题很多时候spfa也可以过)(spfa也可以做正权图的题)

       时间复杂度 一般情况O(m)   最坏情况O(nm)

#include 
#include 
#include 
#include 

using namespace std;

const int N = 100010;

int n, m;
int h[N], w[N], e[N], ne[N], idx;
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 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;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])//对bellman-ford算法进行优化,只将被更新后的点放入队列,用它去更新别人
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    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 = spfa();

    if (t == 0x3f3f3f3f) puts("impossible");
    else printf("%d\n", t);

    return 0;
}
题目:判断是否存在负环
#include #include #include #include using namespace std; const int N = 2010, M = 10010; int n, m; int h[N], w[M], e[M], ne[M], idx; int dist[N], cnt[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 ++ ; } bool spfa() { queue<int> q; for (int i = 1; i <= n; i ++ )//题目问图中是否存在负环,起点是不知道的,所以把所有点都加入 { st[i] = true; q.push(i); } while (q.size()) { int t = q.front(); q.pop(); st[t] = 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]; cnt[j] = cnt[t] + 1; if (cnt[j] >= n) return true;//说明存在负环 if (!st[j]) { q.push(j); st[j] = true; } } } } return false; } 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); } if (spfa()) puts("Yes"); else puts("No"); return 0; }

五、floyd算法(求任意两点间的最短距离)

       时间复杂度O(n^3)

#include 
#include 
#include 

using namespace std;

const int N = 210, INF = 1e9;

int n, m, Q;
int d[N][N];//这里比较特别,是二维的

void floyd()//核心代码
{
    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]);
}

int main()
{
    scanf("%d%d%d", &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;
        scanf("%d%d%d", &a, &b, &c);
        d[a][b] = min(d[a][b], c);
    }

    floyd();

    while (Q -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);

        int t = d[a][b];
        if (t > INF / 2) puts("impossible");
        else printf("%d\n", t);
    }

    return 0;
}

 做题的时候不知道为什么,oj上写0x3f会wa,但是写999999999就可以过。