Dijkstra学习笔记(模板+acwing上的题)

前言和模板

参考文档
第一次接触$Dijkstra$是在大一下复习数据结构期末考试看了下思路。现在刚开始大二下,打卡群写kuangbin专题最短路不得不学下$Dijkstra$🤣,事实证明(1)我很懒(2)人的潜力不容小觑,我曾以为自己永远也看不懂与图相关的算法。

再说下$Dijkstra$的适用前提:

  1. 带权图的最短路径
  2. 权值都为非负值
  3. 单点出发到其它点

言归正传,直接上蒟蒻总结的模板吧。

(1)稠密图

直接用邻接数组的形式存

//从1到n的最短距离
int dij()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0; //含义是到自身的距离为0
    //大循环只是计次数
    for(int _ = 0; _ < n; ++ _)
    {
        int t = -1; //寻找当前的最短路
        for(int i = 1; i <= n; ++ i)
            if(!b[i] && (t == -1 || dist[t] > dist[i])) //短路原则
                t = i;

        //t是所有未到达的点中的最短路
        b[t] = true;
        //尝试更新距离数组
        for(int i = 1; i <= n; ++ i)
            if(dist[t] + a[t][i] < dist[i])
                dist[i] = dist[t] + a[t][i];
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

(2)稀疏图

稀疏图的节点数可高达$10^5$,这个时候用朴素$Dijkstra$算法一定超时。于是优化版的$Dijkstra$算法诞生了。

为什么可以用堆优化?我们发现朴素版的“寻找当前的最短路”和“尝试更新距离”部分是从$1$到$n$硬跑一遍,这一步骤是稀疏图超时的原因。而堆的完全二叉树特性将“找寻当前最短路“的时间复杂度压缩成$log(n)$。

使用邻接链表存储边与边的权值

//求1到n的最短距离
//一个点能被更新的前提是它的前驱被更新
int dij(int n)
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q; //需要小根堆依据距离排序
    q.push({0, 1});
    while(q.size())
    {
        auto tt = q.top(); q.pop();
        int t = tt.second;
        if(b[t]) continue;
        b[t] = true;
        //更新这个点的后继
        for(int i = 0; i < a[t].size(); ++ i)
        {
            int newnode = a[t][i].first, val = a[t][i].second;
            if(dist[newnode] > dist[t] + val)
            {
                dist[newnode] = dist[t] + val;
                q.push({dist[newnode], newnode}); //距离 节点
            }
        }
    }

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

例题 点击题目跳转链接

例一、Dijkstra求最短路 I

板子题,题面不再赘述。

参考代码1 朴素Dijkstra

#include <bits/stdc++.h>

using namespace std;
const int N = 505;
int a[N][N];
int dist[N];
bool b[N];
int n, m; 

//从1到n的最短距离
int dij()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for(int _ = 0; _ < n; ++ _)
    {
        int t = -1;
        for(int i = 1; i <= n; ++ i)
            if(!b[i] && (t == -1 || dist[t] > dist[i]))
                t = i;

        b[t] = true;
        for(int i = 1; i <= n; ++ i)
            if(dist[t] + a[t][i] < dist[i])
                dist[i] = dist[t] + a[t][i];
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    ios::sync_with_stdio(false);
    memset(a, 0x3f, sizeof a);
    cin >> n >> m;

    int x, y, z;
    for(int i = 0; i < m; ++ i)
    {
        cin >> x >> y >> z;
        a[x][y] = min(a[x][y], z);
    }

    cout << dij();
    return 0;
}

参考代码2 堆版本(稠密图不需要堆优化,演示下堆怎么写)

//从1到n的最短距离
int dij()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q; //距离 下标
    q.push({0, 1});
    while(q.size())
    {
        auto tt = q.top(); q.pop();
        int t = tt.second;
        if(b[t]) continue;
        b[t] = true;

        for(int i = 1; i <= n; ++ i)
        {
            int newnode = i, val = a[t][i];
            if(dist[newnode] > dist[t] + val)
            {
                dist[newnode] = dist[t] + val;
                q.push({dist[newnode], newnode});
            }
        }
    }

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

例二、Dijkstra求最短路 II

和例一不同的是结点数高达$10^5$,此时用图的存储方式将由邻接矩阵变为邻接链表

堆优化版代码(不能用朴素法)

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 5e4 + 5;
typedef pair<int, int> PII;
int dist[N];
vector<vector<PII>> a;
bool b[N];

int dij(int n)
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q; //需要小根堆依据距离排序
    q.push({0, 1});
    while(q.size())
    {
        auto tt = q.top(); q.pop();
        int t = tt.second;
        if(b[t]) continue;
        b[t] = true;
        for(int i = 0; i < a[t].size(); ++ i)
        {
            int newnode = a[t][i].first, val = a[t][i].second;
            if(dist[newnode] > dist[t] + val)
            {
                dist[newnode] = dist[t] + val;
                q.push({dist[newnode], newnode});
            }
        }
    }

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

int main()
{
    ios::sync_with_stdio(false);
    int n, m; cin >> n >> m;
    a.resize(n + 1);

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

    cout << dij(n);

    return 0;
}

例三、货物运输

写了两个裸题后再来一道非裸题结个尾吧

题目

给定一个 n 个点 m 条边的无重边无自环的无向图。点的编号为 1∼n

现在,要从点 1 到点 n运货。

已知每条边的最大承重,请你计算一次最多可以运多少货物。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

每组数据第一行包含两个整数 n,m

接下来 m 行,每行三个整数 a,b,c,表示点 a 和点 b 之间有一条无向边,最大承重为 c

输出格式

每组数据先输出一行 Scenario #x,其中 x 是组别编号(从 1 开始)。

然后一行,输出一个整数,表示一次性最多可运货物数量。

最后输出一个空行。

数据范围

$1≤T≤10,$
$1≤n≤1000,$

$1≤m≤\frac{n(n−1)}{2}$,
$1≤a,b≤n,$
$1≤c≤10^6$。
保证一定有解。

输入样例:

1
3 3
1 2 3
1 3 4
2 3 5

输出样例:

Scenario #1:
4

问题分析

  1. 权值都非负且问题和"最短路"相关,用$Dijkstra$算法
  2. 求的值是所有路径中最小值集合的最大值
  3. $dist$数组存最小的承重量即可,越大越好

参考代码1 朴素Dijkstra

#include <bits/stdc++.h>

using namespace std;
typedef pair<int, int> PII;
const int N = 1005;
int a[N][N];
int dist[N];
bool b[N];

int dij(int n)
{
    dist[1] = 2e9;
    for(int _ = 0; _ < n; ++ _)
    {
        int t = -1;
        for(int i = 1; i <= n; ++ i)
            if(!b[i] && (t == -1 || dist[t] < dist[i]))
                t = i;
        b[t] = true;

        for(int i = 1; i <= n; ++ i)
            if(dist[i] < min(dist[t], a[t][i]))
                dist[i] = min(dist[t], a[t][i]);   
    }
    return dist[n];
}

int main()
{
    ios::sync_with_stdio(false);
    int T, n, m; cin >> T;

    for(int i = 1; i <= T; ++ i)
    {
        memset(a, 0, sizeof a);
        memset(dist, 0, sizeof dist);
        memset(b, 0, sizeof b);
        int n, m; cin >> n >> m;
        int x, y, z;
        for(int _ = 0; _ < m; ++ _)
        {
            cin >> x >> y >> z;
            a[x][y] = a[y][x] = z;
        }
        cout << "Scenario #" << i << ":" << endl;
        cout << dij(n) << endl << endl;
    }

    return 0; 
}

参考代码2 堆版本

int dij(int n)
{
    priority_queue<PII> q; //从1到n越大越好
    q.push({2e9, 1});
    while(q.size())
    {
        auto [v, t] = q.top(); q.pop();
        if(b[t]) continue;
        b[t] = true;
        for(int i = 1; i <= n; ++ i)
        {
            if(min(v, a[t][i]) > dist[i]) //当前承重和新的承重
            {
                dist[i] = min(v, a[t][i]);
                q.push({dist[i], i});
            }
        }
    }

    return dist[n];
}

总结

  1. 模板写在了开头
  2. 求最短路时要注意题意是最大值还是最小值;相加还是取最小(大)值
  3. 最小值用小根堆;最大值用大根堆
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇