最短路算法之Dijkstra(一)

目录

  • 1.图的存储(先决条件)
    • 1.1邻接矩阵
    • 1.2邻接表
  • 2.Dijkstra算法(正文)
    • 2.1适用范围
    • 2.2基本定理的简易证明
    • 2.3算法流程
      • 2.3.1基本实现
      • 2.3.2优先队列优化
  • 3.相关练习

1.图的存储

有一种“链式前向星”的做法,这里暂不介绍.

1.1邻接矩阵

存在一个二维数组g,g[i][j] = w 表示点i 到点j 有一条权值为w 的边.

优点: 写起来方便快捷.
缺点: 可扩展性低,存在冗余的内存消耗,使得内存消耗大.

code:

(略)

1.2邻接表

对于点i,存在一个向量vec[u] ,vec[u] 中存储着多个形如 {v, w} 的pair结构,表示点u 存在一条到点v权值为w 的边.

优点: 可以使用STL中的vector,动态拓展内存,无冗余的内存消耗.
缺点: 几乎没有,是常用的做法.

code:

#include <bits/stdc++.h>
using namespace std;
const int N = 10086 * 100;
vector<pair<int, int> > vec[N];
int main()
{
	//这里以无向图作为例子,假设点u与点v间存在权值为w的边
	int u, v, w;
	cin >> u >> v >> w;
	vec[u].push_back({v, w});
	vec[v].push_back({u, w});
	return 0;
}

2.Dijkstra算法

2.1适用范围

先说结论,dijkstra只适用于无负边权有向或无向图.
接下来的定理证明会详细阐述这些内容.

2.2基本定理的简易证明

首先,我们有一个无负边权图g ,d[i] 表示起点s到点i的最短距离.
集合A中的数i表示d[i] 已经是最小值,不可再松弛.

显而易见,d[s] = 0 ,这个值是一个常数,所以这是最小值。此时,A = {s} .
再找到一条权值最小的连接s的边{u, v, w} ,其中u = s,w 在所有u = s 的边中最小.
我们知道,图g 是无负边权图, 而d[s] = 0 这个值最小,所以不存在一条边能使得d[v] < d[s] + w (只有无负边权图满足)
所以d[s] + w 的值对于d[v] 是最小的不可再松弛,此时,A = {s,v} .

同理,我们便可以使用这个d[v]再去更新其他点.

2.3算法流程

2.3.1基本实现

我们使用1.2中的邻接表来存图.
d[i] 表示起点s到点i的距离,这个值应该是不断变小的,所以我们把他的初值设为INF .
vis[i] 为true时表示点i 不可再松弛,我们将vis的初值设为false.

然后我们就知道,d[s] = 0 .
接着,不断如此循环:从起点开始找一个d[i] 值最小的点,满足vis[i] = false ,将vis[i] = true .
然后遍历他的所有出边,找到权值最小的{u, v, w} ,若d[v] > d[u] + w ,便更新d[v] = d[u] + w .
只要所有点i 的vis[i] = true ,就会结束循环,时间复杂度应该是:
O ( N 2 ) . O(N^2) .

code:(使用1.2邻接表存图)

(较容易,略)

2.3.2优先队列优化

2.3.1中算法的复杂度不够理想,运用在复杂的题目中有可能会TLE .

通过分析2.3.1中的算法,我们发现,找 “d[i] 值最小的点” 这个步骤可以进行优化.
可以使用优先队列,保证每次取出的队头元素的d[i] 都是最小.

每次取队头元素这个操作的时间复杂度是:
O ( log 2 N ) O(\log_2N)
所以优先队列优化后的时间复杂度应该是:
O ( M log 2 N ) O(M \cdot \log_2N)

code:(以有向无负边权图为例子)

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

const int N = 1008600;

vector<pair<long long, long long> > g[N];
long long d[N];
bool vis[N];
priority_queue<pair<long long, long long> > q;

int main()
{
    memset(vis, false, sizeof vis);
    long long n, m, s;
    scanf("%lld %lld %lld", &n, &m, &s);//输入n、m、s,分别对应点数、边数、起点
    for(long long i = 1; i <= n; i++) d[i] = 2147483647;
    for(long long i = 0; i < m; i++) {
        long long u, v, w;
        scanf("%lld %lld %lld", &u, &v, &w);//有向图,这里u,v,w指点u到点v有一条权值为w的边
        g[u].push_back({v, w});
    }

    d[s] = 0;
    q.push({0, s});
    while(!q.empty()) {
        long long u = q.top().second;
        q.pop();
        if(vis[u]) continue;
        vis[u] = true;

        for(size_t i = 0; i < g[u].size(); i++) {
            long long v = g[u][i].first,
                               w = g[u][i].second;
            if(d[v] > d[u] + w) {
                d[v] = d[u] + w;
                q.push({-d[v], v}); //让优先队列从小到大
            }
        }
    }
    for(long long i = 1 ; i <= n; i++) printf("%lld ", d[i]);
    return 0;
}

相关练习

模板题:
洛谷P3371
洛谷P4779
非模板题:
洛谷P1144

JiansYuan
2020.8.3

猜你喜欢

转载自blog.csdn.net/qq_41095952/article/details/107753055