搜索与图论- Dijkstra 算法

简介: 搜索与图论- Dijkstra 算法

文章目录


一、Dijkstra 算法

1. 简介

2. 基本思想

3. 朴素 Dijkstra 算法(重点)

3.1 朴素 Dijkstra 算法实现步骤

3.2 朴素 Dijkstra 算法伪代码

4. 朴素 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 I 。

5. 堆优化朴素 Dijkstra 算法

6. 堆优化 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 II 。

7. 堆优化 Dijkstra 算法和朴素 Dijkstra 算法时间复杂度

二、Dijkstra 算法例题——Dijkstra 求最短路 I

具体实现

1. 样例演示

2. 实现思路

3. 代码注解

4. 实现代码

三、Dijkstra 算法例题——Dijkstra求最短路 II

具体实现

1. 样例演示

2. 实现思路

3. 代码注解

4. 实现代码



一、Dijkstra 算法

1. 简介


Dijkstra(迪杰斯特拉)算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。

这是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。

迪杰斯特拉算法主要特点是从起始点开始,采用 贪心算法 的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

此算法不能用于求负权图,要求所有边的权重都为非负值。


2. 基本思想


1) 首先假定源点为 u ,顶点集合 V 被划分为两部分:集合 S 和 V-S。 初始时 S 中仅含有源点 u ,其中 S 中的顶点到源点的最短路径已经确定。

(2) 集合 S 和 V-S 中所包含的顶点到源点的最短路径的长度待定,称从源点出发只经过 S 中的点到达 V-S 中的点的路径为特殊路径,并用 dist[] 记录当前每个顶点对应的最短特殊路径长度。


3. 朴素 Dijkstra 算法(重点)

3.1 朴素 Dijkstra 算法实现步骤


(1) 各个点状态初始化,各点之间距离初始化。

用一个 dist 数组保存源点到其余各个节点的距离,dist[i] 表示源点到节点 i 的距离。初始时,dist 数组的各个元素为无穷大。

用一个状态数组 state 记录是否找到了源点到该节点的最短距离,state[i] 如果为真,则表示找到了源点到节点 i 的最短距离,state[i] 如果为假,则表示源点到节点 i 的最短距离还没有找到。初始时,state 各个元素为假。

7ced2a39ebe041b9bc053bf285bdaf9c.png



(2) 源点到源点的距离为 0,即 dist[1] = 0


d664a5df46a14bd08564f79a6c7a5861.png


(3) 遍历 dist 数组,找到一个节点,这个节点是:没有确定最短路径的节点中距离源点最近的点。假设该节点编号为 i 。此时就找到了源点到该节点的最短距离,state[i] 置为 1 。


693c1fd247e74d39857c5b4c37058ebe.png


4) 遍历 i 所有可以到达的节点 j,如果 dist[j] 大于 dist[i] 加上 i -> j 的距离,即 dist[j] > dist[i] + w[i][j](w[i][j] 为 i -> j 的距离) ,则更新 dist[j] = dist[i] + w[i][j] 。

42edab1ff63843f2a86848f64a19cc89.png

5) 重复 3 4 步骤,直到所有节点的状态都被置为 1。


7a3fd6cc2d9241d095647f353573e5af.png


  • (6) 此时 dist 数组中,就保存了源点到其余各个节点的最短距离。


06438f402d854a9296816fd54a5526de.png



.2 朴素 Dijkstra 算法伪代码

int dist[n],state[n];
dist[1] = 0, state[1] = 1;
for(i:1 ~ n)
{
    t <- 没有确定最短路径的节点中距离源点最近的点;
    state[t] = 1;
    更新 dist;
}



4. 朴素 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 I 。

5. 堆优化朴素 Dijkstra 算法


  • 朴素 Dijkstra 算法的主要耗时的步骤是从 dist 数组中选出,没有确定最短路径的节点中距离源点最近的点 t 。只是找个最小值而已,没有必要每次遍历一遍 dist 数组。
  • 在一组数中每次能很快的找到最小值,很容易想到使用小根堆。可以使用库中的小根堆(推荐)或者自己编写。


具体思路如下:

(1) 起始点的距离初始化为零,其他点初始化成无穷大。

(2) 将起始点放入堆中。

(4) 不断循环,直到堆空。每一次循环中执行的操作为:弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定),用该点更新临界点的距离,若更新成功就加入到堆中。


6. 堆优化 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 II 。

7. 堆优化 Dijkstra 算法和朴素 Dijkstra 算法时间复杂度

  • 朴素 Dijkstra 算法时间复杂度 O(n2) 。
for (int i = 0; i < n; i++) 
{
    int t = -1;
    for (int j = 1; j <= n; j++)
    {
        if (!st[j] && (t == -1 || dist[j] < dist[t]))
            t = j; // O(n) * O(n) -> O(n^2)
    }
    st[t] = true; // O(n) * O(1) -> O(n)
    for (int j = 1; j <= n; j++)
    {
        dist[j] = min(dist[j], dist[t] + g[t][j]); //O(n) * O(n) -> O(n^2)
    }
}
  • 堆优化 Dijkstra 算法时间复杂度 O(mlog(n)) 。
//遍历大部分的边
    while (heap.size()) 
    {
        auto t = heap.top(); //O(m) * O(1) -> O(m)
        heap.pop();
        int ver = t.second, distance = t.first;
        if (st[ver])
        {
            continue;
        }
        st[ver] = true;  //O(m) * O(1) -> O(m)
        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i]) 
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
                // 堆的插入操作时间复杂度是 O(log(n))
                // O(m) * O(log(n)) -> O(mlog(n))
            }
        }
    }



二、Dijkstra 算法例题——Dijkstra 求最短路 I

题目描述


给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。


输入格式


第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。


输出格式


输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。


数据范围


1 ≤ n ≤ 500

1 ≤ m ≤ 1e5

图中涉及边长均不超过10000 。


输入样例


3 3

1 2 2

2 3 1

1 3 4


输出样例


3


具体实现

1. 样例演示

  • 输入 n = 3,m = 3,表示求从 1 号点到 n = 3 号点的最短距离,共有 m = 3 条边。
  • 从 1 号点到 2 号点的边长为 2 。
  • 从 2 号点到 3 号点的边长为 1 。
  • 从 1 号点到 3 号点的边长为 4 。


e438870defba4893a35732c43ea570ac.png


  • 显然,1 + 2 小于 4 ,因此最短路径存在,且为 3 。

2. 实现思路


在此只作简单叙述,具体可见朴素 Dijkstra 算法实现步骤和样例演示。

(1) 初始化各点到起点距离为正无穷。

(2) 将 1 号点到起点的距离更新为 0 。

(3) 2 号点到 1 号点的距离为 2 ,小于正无穷,将其更新为 2 ; 3 号点同理,将距离更新为 4 。

(4) 然后,2 和 4 的最小值是 2 ,便将 2 加入集合 s ,此时,2 号点到 3 号点的距离 1 。可得,2 + 1 < 4,便将 3 号点到起点的距离更新为 3 。


3. 代码注解


int g[N][N];稠密图一般使用邻接矩阵。

int dist[N];记录每个节点距离起点的距离。

bool st[N];true 表示已经确定最短路 属于 s 集合。

memset(dist, 0x3f, sizeof dist);所有节点距离起点的距离初始化为无穷大。

dist[1] = 0;起点距离自己的距离为零。

int t = -1;一开始 t 的赋值是 -1 ,如果 t 没有被更新,那么要更新一下 t 。

memset 按字节赋值,所以 memset 0x3f 就等价与赋值为 0x3f3f3f3f 。

其他代码注解已标注在实现代码当中,详见实现代码。


4. 实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    //迭代n次,每次可以确定一个点到起点的最短路
    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
        {
            //不在s集合,并且
            //如果没有更新过,则进行更新, 或者发现更短的路径,则进行更新
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
            {
                t = j;
            }
        }
        //找到了距离最小的点t,并用最小的点t去更新其他的点到起点的距离
        for (int j = 1; j <= n; j ++ )
        {
            dist[j] = min(dist[j], dist[t] + g[t][j]);
        }
        //加入到s集合中
        st[t] = true;
    }
    // 如果起点到达不了n号节点,则返回-1
    if (dist[n] == 0x3f3f3f3f) 
    {
        return -1;
    }
    // 返回起点距离n号节点的最短距离
    return dist[n];
}
int main()
{
    cin >> n >> m;
    memset(g, 0x3f, sizeof g);
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);
    }
    cout << dijkstra() << endl;
    system("pause");
    return 0;
}


三、Dijkstra 算法例题——Dijkstra求最短路 II

题目描述


给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。


输入格式


第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。


输出格式


输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。


数据范围

1 ≤ n,m ≤ 1.5×1e5

图中涉及边长均不小于 0,且不超过 10000。

数据保证:如果最短路存在,则最短路的长度不超过 1e9。

输入样例

3 3

1 2 2

2 3 1

1 3 4

输出样例

3


具体实现

1. 样例演示

  • 同 Dijkstra 算法例题——Dijkstra求最短路 I 。


2. 实现思路

  • 不需要手写堆,使用 C++ 当中的优先队列。
  • 小根堆使用 greater 实现。


3. 代码注解


typedef pair<int, int> PII;<离起点的距离, 节点编号>。

memset(dist, 0x3f, sizeof dist);所有距离初始化为无穷大。

dist[1] = 0;1 号节点距离起点的距离为 0 。

priority_queue<PII, vector, greater> heap;建立一个小根堆。

其他代码注解已标注在实现代码当中,详见实现代码。


4. 实现代码

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;//<离起点的距离, 节点编号>
const int N = 1e6 + 10;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
//在 a 节点之后插入一个 b 节点,权重为 c
void add(int a, int b, int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
    idx ++ ;
}
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    //1号节点插入堆
    heap.push({0, 1});
    while (heap.size())
    {
        //取出堆顶顶点
        auto t = heap.top();
        //并删除
        heap.pop();
        //取出节点编号和节点距离
        int ver = t.second;
        int distance = t.first;
        //如果节点被访问过则跳过
        if (st[ver]) 
        {
            continue;
        }
        st[ver] = true;
        for (int i = h[ver]; i != -1; i = ne[i])
        {
            //取出节点编号
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }
    if (dist[n] == 0x3f3f3f3f)
    {
        return -1;
    }
    return dist[n];
}
int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    cout << dijkstra() << endl;
    system("pause");
    return 0;
}












相关文章
|
1月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
70 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
17天前
|
算法 搜索推荐 数据库
二分搜索:高效的查找算法
【10月更文挑战第29天】通过对二分搜索的深入研究和应用,我们可以不断挖掘其潜力,为各种复杂问题提供高效的解决方案。相信在未来的科技发展中,二分搜索将继续发挥着重要的作用,为我们的生活和工作带来更多的便利和创新。
26 1
|
1月前
|
算法 决策智能
基于禁忌搜索算法的VRP问题求解matlab仿真,带GUI界面,可设置参数
该程序基于禁忌搜索算法求解车辆路径问题(VRP),使用MATLAB2022a版本实现,并带有GUI界面。用户可通过界面设置参数并查看结果。禁忌搜索算法通过迭代改进当前解,并利用记忆机制避免陷入局部最优。程序包含初始化、定义邻域结构、设置禁忌列表等步骤,最终输出最优路径和相关数据图表。
|
2月前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
在数据密集型应用中,高效搜索算法至关重要。Trie树(前缀树/字典树)通过优化字符串处理和搜索效率成为理想选择。本文通过Python实战演示Trie树构建与应用,显著提升搜索性能。Trie树利用公共前缀减少查询时间,支持快速插入、删除和搜索。以下为简单示例代码,展示如何构建及使用Trie树进行搜索与前缀匹配,适用于自动补全、拼写检查等场景,助力提升应用性能与用户体验。
57 2
|
1月前
|
存储 算法 程序员
迪杰斯特拉(Dijkstra)算法(C/C++)
迪杰斯特拉(Dijkstra)算法(C/C++)
|
1月前
|
存储 算法 C++
【搜索算法】 跳马问题(C/C++)
【搜索算法】 跳马问题(C/C++)
|
1月前
|
人工智能 算法 Java
【搜索算法】数字游戏(C/C++)
【搜索算法】数字游戏(C/C++)
|
29天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
6天前
|
算法 数据安全/隐私保护 索引
OFDM系统PAPR算法的MATLAB仿真,对比SLM,PTS以及CAF,对比不同傅里叶变换长度
本项目展示了在MATLAB 2022a环境下,通过选择映射(SLM)与相位截断星座图(PTS)技术有效降低OFDM系统中PAPR的算法实现。包括无水印的算法运行效果预览、核心程序及详尽的中文注释,附带操作步骤视频,适合研究与教学使用。
|
14天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。