算法刷题总结—最短路径算法1(Dijkstra)

常用的最短路径问题方法总结如下:

 1.Dijkstra算法

适用场景:单源最短路—要求没有负权边

Dijkstra本质是贪心的思想,每次选择当前到起点距离最短的边。包括朴素Dijkstra和堆优化版的Dijkstra

朴素Dijkstra适用于稠密图(边的数量远大于结点数量),堆优化版的Dijkstra适用于稀疏图

Dijkstra算法演示流程:

假设有如下的图,起始结点为a,设结果集和为s,初始时s为空,当最终所有的结点都加入到s中时,算法结束

 

 Dijskstra需要维护一个距离数组dist[N],表示某个点到起点的最短距离,初始状态如下:

a

b

c

d

e

f

+

+

+

+

+

+

 首先更新起点,将起点a的dist数组的值更新为0,并将起点a加入到结果集和s,更新后的dist数组状   态如下:

a

b

c

d

e

f

0

+

+

+

+

+

由集和s内的点,更新其它点到起点的距离,与a相邻的点有b和d,最短距离分别为6和4,选择最短的距离的点d加入结果集和,更新dist数组

a

b

c

d

e

f

0

+

+

4

+

+

此时结果集和内有a和d,再用这两个点更新其它点到起点的距离,起点可以到的点有b,f,c,最短距离分别为6,7,5,选择c加入结果集合,更新dist数组

a

b

c

d

e

f

0

+

5

4

+

+

此时s内有a,c,d,起点可以到的点有b,f,e,最短距离分别为6,7,12,将b加入结果集和s

a

b

c

d

e

f

0

6

5

4

+

+

此时s内有a,b,c,d,起点可以到的点有e和f,最短距离分别为12和7,将f加入结果集合s

a

b

c

d

e

f

0

6

5

4

+

7

此时s内有a,b,c,d,起点可以到的点有e,最短距离为9,将e加入结果集和s

a

b

c

d

e

f

0

6

5

4

9

7

此时所有结点已全部加入集和s,更新完成

时间复杂度分析:

Dijstra算法包括:

for i 1→ n     遍历n次(假设有n个点),每次将一个点加入结果集和s

        for j 1→n   找到当前距离起点最近的结点加入集和   

        for k 1→n  利用新加入的结点更新起点到其他所有结点的距离

所以朴素Dijstra的时间复杂度为O(2n²)=O(n²)

堆优化版的Dijkstra主要对寻找最近的结点这一步进行优化,采用最小堆(优先队列)来提高时间效率

下面是两道Dijkstra算法的问题,分别基于朴素Dijkstra和对堆优化版的Dijkstra

朴素Dijkstra

问题描述

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

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

输入格式

第一行包含整数 n 和 m。

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

输出格式

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

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

数据范围

1≤n≤500,
1≤m≤10^5,
图中涉及边长均不超过10000。

输入样例

3 3
1 2 2
2 3 1
1 3 4

输出样例

3

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

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;
    for(int i = 0; i < n; i ++){
        int min_id = -1;
        for(int j = 1; j <= n; j ++){
            //寻找到起始点最短距离的点, 加入结果集和
            if(!st[j] && (min_id == -1 || dist[j] < dist[min_id]))
                min_id = j;
        }
        st[min_id] = true;
        for(int j = 1; j <= n; j ++){
            dist[j] = min(dist[j], dist[min_id] + g[min_id][j]);
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main(){
    scanf("%d%d", &n, &m);
    memset(g, 0x3f, sizeof g);
    for(int i = 0; i < m; i ++){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        g[x][y] = min(g[x][y], z);   //有重边的情况,只保留两个结点间的最短边
    }
    cout << dijkstra() << endl;
    return 0;
}

说明

题目中n和m的范围可以看出这道题是稠密图

dist数组维护起点到每个点的最短距离,st数组记录每个结点是否被选过,防止在更新最近距离点的时候更新到已选过的节点

需要注意的是,这道题可能会有重边和还,处理方式是环不考虑(无负权边),重边只保留最短的那条边

同时要注意memset的用法(按字节赋值)

堆优化版的Dijkstra

问题描述

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

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

输入格式

第一行包含整数 n 和 m。

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

输出格式

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

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

数据范围

1≤n,m≤1.5×10^5,
图中涉及边长均不小于 00,且不超过 10000。
数据保证:如果最短路存在,则最短路的长度不超过 10^9。

输入样例

3 3
1 2 2
2 3 1
1 3 4

输出样例

3

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 150010;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];

void add(int a, int b, int c){
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx ++;
}

int dijkstra(){
    memset(dist, 0x3f, sizeof dist);
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    dist[1] = 0;
    heap.push({0, 1});
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        int distance = t.first, id = t.second;
        if(st[id]) continue;
        st[id] = true;
        for(int i = h[id]; 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});
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main(){
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for(int i = 0; i < m; i ++){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);
    }
    cout << dijkstra() << endl;
    return 0;
}

说明

C++中小根堆和大根堆的定义如下:

priority_queue<int> maxHeap; // 大根堆

priority_queue<int, vector<int>, greater<int>> minHeap; // 小根堆

这里采用pair存储的原因是,对于一个点的所有路径可能都会存入堆中,默认取堆顶到该点的最短路径,其他的自动忽略即可,因为堆不涉及删除、覆盖操作,没有像朴素Dijkstra一样先筛选最短的加入集和,而是所有可行的路径全加入堆中(pair默认按第一个值排序)

时间复杂度:O(mlogn)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值