领略算法真谛:单源最短路问题

嘿,各位技术潮人!好久不见甚是想念。生活就像一场奇妙冒险,而编程就是那把超酷的万能钥匙。此刻,阳光洒在键盘上,灵感在指尖跳跃,让我们抛开一切束缚,给平淡日子加点料,注入满满的passion。准备好和我一起冲进代码的奇幻宇宙了吗?Let's go!

我的博客:yuanManGan

我的专栏:C++入门小馆 C言雅韵集 数据结构漫游记 闲言碎语小记坊 题山采玉 领略算法真谛 数据结构进阶

什么是单源最短路?假设一个图中的两个顶点vi,vj,从vi到vj路径上所经过的权值之后就称为带权路径长度。

但vi到vj的路径有很多条,将带权路径长度最短的那条路径就称为最短路径。

• 单源最短路,即图中一个顶点到其它各顶点的最短路径。

• 多源最短路,即图中每对顶点间的最短路

我们先来讲解第一个解决方法:dijkstra 算法

该算法只能解决非负权图的单源最短路的问题

算法思想:贪心

每次拿出还未确定最短路径的点中,距离起点最近的点。

我们用dist数组来存储我们最终的结果,用edges来存储图的结构,用bool类型的st数组来标记已经找出有最短路径的顶点,我们可以将dist数组初始化为正无穷表示还未找到其最小路径,dist[1] = 0,表示1顶点是起点。

具体流程,拿出dist不等于∞的顶点指向的下一个顶点中最小权值的顶点(如果相同随便拿一个),更新dist数组,将拿出的顶点和起点视为一个顶点,比较权值更新所有拿出的顶点的边的权值,重复操作。

模板题目:

【模板】单源最短路问题(弱化版)

题目来源: 洛谷

题目链接: P3371 【模板】单源最短路径(弱化版)

难度系数: ★★

常规版 dijkstra 算法:

cpp 复制代码
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
typedef pair<int, int>PII;
const int N = 1e4 + 10, INF = 2147483647;
int dist[N];
bool st[N];
vector<PII>edges[N];
int n, m, s;
void dijkstra()
{
    for (int i = 0; i <= n; i++) dist[i] = INF;
    dist[s] = 0;

    //循环n次找n个顶点的边权情况
    for (int i = 1; i < n; i++)
    {
        //找最小边权
        int u = 0;
        for (int j = 1; j <= n; j++)
            if (!st[j] && dist[j] < dist[u]) u = j;
        //打上标记然后松弛
        st[u] = true;

        for (auto &t : edges[u])
        {
            int v = t.first, w = t.second;
            if (dist[v] > dist[u] + w)
                dist[v] = dist[u] + w;
        }
    }
    for (int i = 1; i <= n; i++) cout << dist[i] << " ";
}
int main()
{
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++)
    {
        int x, y, z; cin >> x >> y >> z;
        edges[x].push_back({ y, z });
    }

    dijkstra();

    return 0;
}

时间复杂度为O(n^2)

我们每次都要找出最小的元素,我们可以建一个小根堆来优化一下时间复杂度

堆优化版 dijkstra 算法

在常规版的基础上,⽤优先级队列去维护待确定最短路的结点,因为我们每次都得去找最小的边权,我们不如创建个小根堆,让搜索的时间复杂度为O(n*lg n)

它可以过这个要求时间复杂度更小的样例

【模板】单源最短路问题(标准版)

题⽬来源: 洛⾕

题⽬链接:P4779 【模板】单源最短路径(标准版)

难度系数: ★★

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>

using namespace std;
const int N = 1e5 + 10;
typedef pair<int, int> PII;
//创建小根堆
priority_queue<PII, vector<PII>, greater<PII>> heap;
vector<PII> edges[N];
int n, m, s;
int dist[N];
bool st[N];

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

    dist[s] = 0;

    heap.push({ 0, s });//权值,顶点
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int v = t.second;
        if (st[v]) continue;
        st[v] = true;

      
        for (auto& x : edges[v])
        {
            int u = x.first, w = x.second;
            
            if (dist[u] > dist[v] + w)
            {
                dist[u] = dist[v] + w;
                heap.push({ dist[u], u });
            }
        }
    }
    for (int i = 1; i <= n; i++) cout << dist[i] << " ";
}

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

    for (int i = 1; i <= m; i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        edges[x].push_back({ y, z });
    }

    dijkstra();

    return 0;
}

但我们的dijkstra算法遇到了负边权时:

这时我们得采用其他算法了。

bellman-ford 算法

dijkstra算法以点出发,而这个bf算法则以边出发,这个算法可以解决边权为负数的情况。

算法核⼼思想:不断尝试对图上每⼀条边进⾏松弛,直到所有的点都⽆法松弛为⽌。

这个依然只能过第一个题目。

代码展示:

cpp 复制代码
#include <iostream>
#include <vector>

using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10, INF = 2147483647;

vector<PII> edges[N];
int dist[N];
int n, m, s;

void bf()
{
    for (int i = 0; i <= n; i++) dist[i] = INF;
    dist[s] = 0;

    // 最多进行 n - 1 次松弛操作
    for (int i = 1; i < n; i++)
    {
        // 每一轮开始时将 flag 初始化为 false
        bool flag = false;
        for (int j = 1; j <= n; j++)
        {
            int u = j;
            for (auto& t : edges[u])
            {
                int v = t.first, w = t.second;
                if (dist[u] == INF) continue;
                if (dist[u] + w < dist[v])
                {
                    dist[v] = dist[u] + w;
                    flag = true;
                }
            }
        }
        // 每一轮结束后判断是否有边被松弛,若没有则提前结束
        if (!flag) break;
    }

    for (int i = 1; i <= n; i++) cout << dist[i] << " ";
}

int main()
{
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        edges[x].push_back({ y, z });
    }

    bf();

    return 0;
}

spfa 算法

该算法依旧是对bf算法的优化,这里使用队列对bf算法进行优化。

在bf算法中很多时候我们不需要进行那么多的无用的松弛操作:

1.只有上一次被松弛的节点,它的出边,才可能引起下一次的松弛操作;

2.因此,如果用队列来维护"那些节点可能会引起松弛操作",就能只访问必要的边了,降低了时间复杂度

虽然在⼤多数情况下 spfa 跑得很快,但其最坏情况下的时间复杂度为 。将其卡到这个复杂度

也是不难的,所以在没有负权边时最好使⽤ Dijkstra 算法。

cpp 复制代码
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
typedef pair<int, int>PII;
const int N = 1e4 + 10, INF = 2147483647;
vector<PII>edges[N];
int dist[N];
bool st[N];
queue<int> q;
int n, m, s;
void spfa()
{
    for (int i = 0; i <= n; i++) dist[i] = INF;
    dist[s] = 0;
    q.push(s);
    st[s] = true;
    while (q.size())
    {
        auto u = q.front(); q.pop();
        st[u] = false;
        for (auto& t : edges[u])
        {
            int v = t.first, w = t.second;

            if (dist[u] == INF) continue;

            if (dist[u] + w < dist[v])
            {
                dist[v] = dist[u] + w;
                if (!st[v])
                {
                    q.push(v);
                    st[v] = true;
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) cout << dist[i] << " ";

}
int main()
{
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++)
    {
        int x, y, z; cin >> x >> y >> z;
        edges[x].push_back({ y, z });
    }
    spfa();
    return 0;
}
相关推荐
艾莉丝努力练剑9 小时前
【Linux:文件】Ext系列文件系统(初阶)
大数据·linux·运维·服务器·c++·人工智能·算法
偷吃的耗子9 小时前
【CNN算法理解】:CNN平移不变性详解:数学原理与实例
人工智能·算法·cnn
dazzle10 小时前
机器学习算法原理与实践-入门(三):使用数学方法实现KNN
人工智能·算法·机器学习
那个村的李富贵10 小时前
智能炼金术:CANN加速的新材料AI设计系统
人工智能·算法·aigc·cann
张张努力变强10 小时前
C++ STL string 类:常用接口 + auto + 范围 for全攻略,字符串操作效率拉满
开发语言·数据结构·c++·算法·stl
万岳科技系统开发10 小时前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
张登杰踩10 小时前
MCR ALS 多元曲线分辨算法详解
算法
YuTaoShao11 小时前
【LeetCode 每日一题】3634. 使数组平衡的最少移除数目——(解法一)排序+滑动窗口
算法·leetcode·排序算法
波波00711 小时前
每日一题:.NET 的 GC是如何分代工作的?
算法·.net·gc
风暴之零11 小时前
变点检测算法PELT
算法