首页 > 编程语言 >最短路算法之:Dijkstra 算法

最短路算法之:Dijkstra 算法

时间:2024-06-09 23:13:09浏览次数:22  
标签:cur int 短路 Dijkstra 算法 dis

最短路系列:Dijkstra 算法

大家好,我是Weekoder!

最短路系列的第二期:Dijkstra 他来啦!

那么废话不多说,让我们直奔主题吧。

Dijkstra 算法的用处

与 floyd 算法不同的,Dijkstra 算法用于求解单源最短路径。顾名思义,单源最短路径就是起点唯一,终点有多个的最短路算法。

Dijkstra 的思想是贪心,而这也将在算法的实现步骤中体现出来。

DIjkstra 算法的思想

Dijkstra 算法是以点为研究对象的最短路算法。我们把图中的点分为两种:黑点和白点。黑点是已经找到最短路的点,白点则相反。一开始所有点都是白点,我们需要将这些点全部染成黑点。用一个 \(vis\) 数组标记,\(vis_i=\text{True}\) 代表 \(i\) 点是黑点,反之则是白点。

我们有一个 \(dis\) 数组,\(dis_i\) 代表从源点到点 \(i\) 的最短路。此时显然有:

\[dis_s=0 \]

\(s\) 为起点。

而其余的则设为 INF(\(\infty\))。又有:

\[dis_i=+\infty(i\ne s) \]

我们每次在 \(dis\) 中选取一个最小值并染成黑色,此时这个点的最短路就是确定的了。既然这个点的最短路是确定的,我们就可以利用这个点来松弛其他点。即对于一条边 \(cur\to nxt\),有:

\[dis_{nxt}=\min(dis_{nxt},dis_{cur}+w),(cur,nxt)\in E \]

如此往复,直到所有点染成黑色。

Dijkstra 算法的正确性

刚才说到 Dijkstra 是将所有点染成黑色,每次找 \(dis\) 中最小的值,这就是一个贪心。那这个贪心是正确的吗?为什么最小的 \(dis\) 的最短路就是确定了的呢?因为如果 \(dis_i\) 是最小的,那么就不可能有更小的方案从其他地方过来。比如现在有 \(dis_1,dis_2,dis_3\),其中 \(dis_1<dis_2<dis_3\),即 \(dis_1\) 最小,就必然有 \(dis_2+dis_3>dis_1\)。这个时候,\(dis_1\) 是怎么也无法更新的。但如果权值为负,这个贪心就不成立了。比如有两条负边权 \(-2\)
和 \(-3\),哪怕对于一个最小的 \(-4\),也可能有 \((-2)+(-3)\le-5\)。还有,Dijkstra 无法跑最长路,因为最长路无法用 Dijkstra 的逻辑实现。

Dijkstra 的实现

根据我们上面的逻辑写即可。这份代码仅能通过P3371

\(\text{Code:}\)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 1e4 + 5;

struct Edge {
	int to, dis;
};

ll n, m, s, dis[N];
bool vis[N];

vector<Edge> nbr[N];

void dijkstra() {
	memset(dis, 0x3f, sizeof dis);
	dis[s] = 0;
	for (int i = 1; i <= n; i++) {
		ll minx = 1e18, x;
		for (int j = 1; j <= n; j++) 
			if (!vis[j] && dis[j] < minx)
				minx = dis[j], x = j;
		vis[x] = 1;
		for (Edge nxt : nbr[x]) {
			ll to = nxt.to, w = nxt.dis;
			dis[to] = min(dis[to], dis[x] + w);
		}
	}	
}

int main() {
	cin >> n >> m >> s;
	for (int i = 1; i <= m; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		nbr[u].emplace_back((Edge){v, w});
	}
	dijkstra();
	for (int i = 1; i <= n; i++)
		cout << (dis[i] == 0x3f3f3f3f3f3f3f3f ? (ll)pow(2, 31) - 1 : dis[i]) << " ";
	return 0;
}

容易发现,Dijkstra 算法的时间复杂度是 \(\Theta(n\cdot(n+m))\)。在 \(m\) 达到上限 \(n^2\) 的时候,算法的时间复杂度约为 \(\Theta(n^3)\),还不如 floyd。当然,在绝大多数情况下,朴素 Dijkstra 算法的时间复杂度都大约为 \(\Theta(n^2)\)。

Dijkstra 算法的优化

考虑优化 Dijkstra 的时间复杂度。对于第一层大循环的 \(\Theta(n)\),是一个个给点染色,无法优化。而对于松弛边的 \(\Theta(m)\),也是必要的,依然无法优化。那么就只能从寻找 \(dis\) 数组的最小值 \(\Theta(n)\) 的复杂度入手。学过二叉堆的都知道有个东西叫优先队列,可以以 \(\Theta(\log n)\) 的复杂度快速维护序列的最值。那我们也可以借助优先队列维护 \(dis\) 的最小值,于是代码的时间复杂度降为 \(\Theta(n\cdot(\log n+m))\approx\Theta(n\log n)\)。

\(\text{Optimal Code:}\)

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;

struct Edge {
    int to, dis;
};
struct node {
    int y, w;
    bool operator<(const node &x)const {
        return w > x.w;
    }
};

int n, m, dis[N];
bool vis[N];

vector<Edge> nbr[N];
priority_queue<node> q;

void dijkstra(int s) {
    memset(vis, 0, sizeof vis);
    memset(dis, 0x3f, sizeof dis);
    dis[s] = 0;
    q.push((node){s, 0});
    while (!q.empty()) {
        node now = q.top();
        q.pop();
        int cur = now.y;
        if (vis[cur]) continue;
        vis[cur] = 1;
        for (auto nxt : nbr[cur]) {
            int to = nxt.to, w = nxt.dis;
            if (dis[to] > dis[cur] + w) {
                dis[to] = dis[cur] + w;
                q.push((node){to, dis[to]});
            }
        }
    }
}

int main() {
    int s;
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        nbr[u].emplace_back((Edge){v, w});
    }
    dijkstra(s);
    for (int i = 1; i <= n; i++)
        cout << dis[i] << " ";
    return 0;
}

(\(\text{Optimal}\) 意为优化)

这份优化后的代码可以通过P4779

小结

关于单源最短路径 Dijkstra 算法的介绍就到这里。Dijkstra 算法的时间复杂度其实相当优秀,是最短路算法的首选。在无权图中,优先选择 BFS,而在正权图中,优先选择 Dijkstra。而遇到负权图,我们又该怎么办呢?让我们一起期待下一次的最短路算法!

再见!

标签:cur,int,短路,Dijkstra,算法,dis
From: https://www.cnblogs.com/Weekoder/p/18240210

相关文章

  • 最短路算法之:floyd 算法
    最短路系列:floyd算法大家好,我是Weekoder!最近学了最短路,我来出一个最短路系列辣!今天的算法是:floyd算法!我会用我自己的理解尽量让大家搞懂floyd算法。floyd算法的用处floyd算法是最短路算法之一,适合求解多源最短路径问题。什么是多源最短路径呢?其实就是起点和终点都有......
  • 最短路算法之:SPFA 算法
    最短路系列:SPFA算法大家好,我是Weekoder!终于,我们的最短路算法SPFA闪亮登场了!虽然话是这么说,但是我还是建议大家先学习Dijkstra算法,再来学习SPFA。并且我们在这里学习的SPFA算法只能通过P3371,并不能通过标准版的P4779。SPFA的原型——Bellman-Ford在学习SPFA之前,我......
  • 代码随想录算法训练营第六天
    哈希表常见的三种哈希结构:数组、set(集合)、map(映射)要快速判断一个元素是否出现集合里,考虑哈希法!242.有效的字母异位词题目:给定两个字符串s和t,编写一个函数来判断t是否是s的字母异位词。注意:若s和t中每个字符出现的次数都相同,则称s和t互为字母异位词。解题:......
  • AcWing算法基础课笔记——最小生成树与二分图
    目录朴素版prim算法——模板题AcWing858.Prim算法求最小生成树题目代码Kruskal算法——模板题AcWing859.Kruskal算法求最小生成树题目代码染色法判别二分图——模板题AcWing860.染色法判定二分图题目代码匈牙利算法——模板题AcWing861.二分图的......
  • AcWing算法基础课笔记——求最短路算法
    目录朴素dijkstra算法——模板题AcWing849.Dijkstra求最短路I题目代码堆优化版dijkstra——模板题AcWing850.Dijkstra求最短路II题目代码Bellman-Ford算法——模板题AcWing853.有边数限制的最短路题目代码spfa算法(队列优化的Bellman-Ford算法)——......
  • (算法)判断是否互为字符重排——<哈希表>
    1.题⽬链接:⾯试01.02.判定是否互为字符重排2.题⽬描述:3.解法(哈希表): 算法思路: 1.当两个字符串的⻓度不相等的时候,是不可能构成互相重排的,直接返回false;2.如果两个字符串能够构成互相重排,那么每个字符串中「各个字符」出现的「次数」⼀定是相同的。因此,我们可以......
  • 程序分享--常见算法/编程面试题:最长公共前缀
    关注我,持续分享逻辑思维&管理思维&面试题;可提供大厂面试辅导、及定制化求职/在职/管理/架构辅导;推荐专栏《10天学会使用asp.net编程AI大模型》,目前已完成所有内容,持续上传中。一顿烧烤不到的费用,让人能紧跟时代的浪潮。从普通网站,到公众号、小程序,再到AI大模型网站。干货满满......
  • TypeScript算法每日一题:最富有客户的资产总量(1672)
    作者:前端小王hs阿里云社区博客专家/清华大学出版社签约作者✍/CSDN百万访问博主/B站千粉前端up主题库:力扣题目序号:1672(简单)题目:最富有客户的资产总量给你一个mxn的整数网格accounts,其中accounts[i][j]是第i​​​​​​​​​​​​位客户在第j家银行托管的资产数......
  • 代码随想录算法训练营第十四天|二叉树递归遍历、迭代遍历、统一迭代
    二叉树遍历二叉树主要有两种遍历方式:深度优先遍历:先往深走,遇到叶子节点再往回走。深度优先遍历又分:前序遍历(中、左、右)中序遍历(左、中、右)后序遍历(左、右、中)广度优先遍历:一层一层的去遍历。(后面讲)递归遍历递归三要素确定递归函数的参数和返回值:确定哪些参数是递......
  • 模拟退火(Simulated Annealing, SA)算法是
    模拟退火(SimulatedAnnealing,SA)算法是一种概率型启发式搜索算法,用于寻找优化问题中的全局最优解。它受到冶金学中退火过程的启发,通过模拟金属冷却过程中的退火过程来寻找问题的最优解。以下是使用MATLAB实现模拟退火算法的一个简单示例。这个例子中,我们将使用模拟退火算法来......