最短路算法超详细讲解

本文出自我的掘金博客, 欢迎大家访问传送门

首先奉上一道模板题 传送门

我第一个要介绍的方法是dijikstra算法, 算法的思想我想不必介绍了, 大家都知道

dijkstra的无优化版本核心代码

    if(map[i][j]>map[i][k]+map[k][j])
        map[i][j]=map[i][k]+map[k][j];
    //其实就是一个松弛操作而已嘛,这就是Dijkstra与Floyd的核心思想,Dijkstra就是把时间复杂度降低,范围缩小而已

它的一个最简单的也可以大幅度提升性能的优化当然就是堆优化啦, 其实也没多难, 只用一个优先队列即可, 优先队列的底层实现就是堆, 直接调库, 可以节省手写堆的时间, 还可以避免出错 何乐而不为呢?

对于每一个加入队列的节点,我们考虑用pair来存。当然写结构体也可以,但是pair可以更加的方便。

typedef pair pa;
priority_queue , cmp > q;

struct cmp
{
    bool operator()(const pa p1, const pa p2)
    {
        return p1.second > p2.second; //second的小值优先
    }
};
//这里注意要传入比较函数, 由于pair的第二个值代表着距离, 且要求小根堆, 故这样写cmp函数

接下来我们采用链式前向星来存储边, 至于为什么用这个? 因为我看见评论区神犇都在用, 肯定有其过人之处

第一步, 建立边结构体

struct Edge {
    int next;
    int to;
    int w;
} e[100000]; //这里至少要开双倍那么大, 因为是无向图, 双向都要加边哒, 我在这里踩过坑

其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].w为边权值.

此外, 我们还需要一个head数组, 它是用来表示以i为起点的第一条边存储的位置,实际上你会发现这里的第一条边存储的位置其实在以i为起点的所有边的最后输入的那个编号.你模拟一遍这个过程就会知道为什么, 因为后面的会覆盖前面的.

第二步, 写加边的函数, 我们需要一个初始值为0的变量cnt.

void addEdge(int u, int v, int w) {
    e[++cnt].w = w;
    e[cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt; //head[i]保存的是以i为起点的所有边中编号最大的那个,而把这个当作顶点i的第一条起始边的位置.
}

第三步, 写我们的dijkstra函数, 注释已经很详细了

void dijkstra () {
    priority_queue , cmp > q;//初始化优先队列

    q.push(make_pair(s, 0)); //将源点放入优先队列

    //初始化为一个很大的值
    for (int i = 1; i <= n; i++) {
        dis[i] = 2e9;
    }
    //将源点赋值为0
    dis[s] = 0;

    while (!q.empty()) {
        int now = q.top().first;
        int val = q.top().second;

        q.pop();
        //遍历每一个顶点相连的所有边, 并进行松弛操作
        for (int i = head[now]; i; i = e[i].next) {
            int to = e[i].to;
            //松弛操作不解释
            if (dis[to] > e[i].w + val) {
                dis[to] = e[i].w + val;
                q.push(make_pair(to, dis[to]));
            }
        }
    }
}

最后附上Dijkstra算法优化版本的完整代码

//打算尝试一下Dijkstra算法的链式前向星写法以及堆优化

#include 

using namespace std;

typedef pair pa;
int n, m, s, t, head[10000], cnt, dis[10000];

//建立边结构体,其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].w为边权值.
struct Edge {
    int next;
    int to;
    int w;
} e[100000]; //这里至少要开双倍那么大, 因为是无向图, 双向都要加边哒

struct cmp
{
    bool operator()(const pa p1, const pa p2)
    {
        return p1.second > p2.second; //second的小值优先
    }
};


void addEdge(int u, int v, int w) {
    e[++cnt].w = w;
    e[cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt; //head[i]保存的是以i为起点的所有边中编号最大的那个,而把这个当作顶点i的第一条起始边的位置.
}

void dijkstra () {
    priority_queue , cmp > q;

    q.push(make_pair(s, 0)); //将源点放入优先队列

    for (int i = 1; i <= n; i++) {
        dis[i] = 2e9;
    }
    dis[s] = 0;

    while (!q.empty()) {
        int now = q.top().first;
        int val = q.top().second;

        q.pop();
        for (int i = head[now]; i; i = e[i].next) {
            int to = e[i].to;

            if (dis[to] > e[i].w + val) {
                dis[to] = e[i].w + val;
                q.push(make_pair(to, dis[to]));
            }
        }
    }

}

int main() {
    cin >> n >> m >> s >> t;

    int u, v, w;
    for (int i = 1; i <= m; i++) {
        cin >> u >> v >> w;
        addEdge(u, v, w);
        addEdge(v, u, w);
    }

    dijkstra();

    cout << dis[t];
    return 0;
}

接下来介绍Bellman-Ford算法

对于这个算法只是稍微优化了一下就取得了不错的性能

以下操作循环执行至多n-1次,n为顶点数:
对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;

用代码描述就是

dis[s] = 0;
    for (int i = 1; i < n; i++) {
        check = true;
        for (int i = 1; i <= m; i++) {
            if (dis[v[i]] > dis[u[i]] + w[i]) {
                check = false;
                dis[v[i]] = dis[u[i]] + w[i];
            }

            if (dis[u[i]] > dis[v[i]] + w[i]) {
                check = false;
                dis[u[i]] = dis[v[i]] + w[i]; 
            }
        }

        if (check) {
            break;
        }
    }

敲重点, 其中check的作用就是当没有可以松弛的点时, 就说明算法以及结束, 不用遍历n- 1轮了, 直接跳出算法

下面附上完整代码

#include 

using namespace std;

const int MAXN = 2500 + 10;
const int MAXM = 6200 + 10;
const int INF = 2e9;
int u[MAXM], v[MAXM], dis[MAXN], w[MAXM];
bool check;
int n, m, s, t;

int main() {
    cin >> n >> m >> s >> t;

    for (int i = 1; i <= m; i++) {
        cin >> u[i] >> v[i] >> w[i];
    }

    for (int i = 1; i <= n; i++) {
        dis[i] = INF;
    }

    dis[s] = 0;
    for (int i = 1; i < n; i++) {
        check = true;
        for (int i = 1; i <= m; i++) {
            if (dis[v[i]] > dis[u[i]] + w[i]) {
                check = false;
                dis[v[i]] = dis[u[i]] + w[i];
            }

            if (dis[u[i]] > dis[v[i]] + w[i]) {
                check = false;
                dis[u[i]] = dis[v[i]] + w[i]; 
            }
        }

        if (check) {
            break;
        }
    }

    cout << dis[t];
    return 0;
}

你可能感兴趣的:(算法)