Dijkstra算法用于解决单源最短路径问题,通过逐个收录顶点来确保已收录顶点的路径长度为最短。

    

图片来自陈越姥姥的数据结构课程:https://mooc.study.163.com/learn/1000033001?tid=1000044001#/learn/content?type=detail&id=1000112011&cid=1000126096

Dijkstra算法的时间复杂度,取决于“V=未收录顶点中dist最小者”的算法。这一步可以用线性查找实现,也可以用最小堆实现。

线性查找的算法就不用多说了。最小堆的算法有一个问题:最小堆是以未收录顶点的dist作为key来建立的,但是每一轮循环都会把部分顶点的dist值改变,也就会破坏最小堆的有序性,怎么解决?

显然应该在每一轮循环中把最小堆重新调整成有序。现在问题又来了:

1. 复杂度还合算吗?

建堆的时间复杂度是O(N),pop一个元素的时间复杂度是O(logN);线性查找的时间复杂度也是O(N)。建堆还额外使用了O(N)的空间。

看似一点都不合算。但我又想到每一轮循环中的建堆操作,很可能只需要调整少量元素,而对于其他元素,只需要进行访问。然而线性查找连调整都不需要,只有交换。再然而,循环过程中堆会变小,使建堆的时间复杂度中的常数变小。至于到底哪个更快,还得实践出真知。

所以只能从消除建堆操作入手。这样又是另一种算法了,参考资料[1]给出了详细说明,这种算法中每一轮的时间复杂度为O(logN),总时间复杂度为O(|E|log|V|)(V为顶点,E为边)。

2. 如何利用STL进行堆操作?

STL <algorithm> 头文件提供了 std::is_heap 、 std::is_heap_until (这两个需要C++11)、 std::make_heap 、 std::push_heap 、 std::pop_heap 和 std::sort_heap 等函数模板用于堆操作。

现有一道单源最短路径的题:https://pintia.cn/problem-sets/994805342720868352/problems/994805523835109376,Dijkstra算法的变形而已。

以下为实现代码。三种算法用宏定义选择,已选择优先队列算法。

 #include <iostream>
#include <limits>
#include <vector>
#include <queue>
#include <algorithm>
#include <utility>
#include <functional> //#define LINEAR
//#define HEAP
#define QUEUE struct Path
{
Path() = default;
Path(int _city, int _dist)
: city(_city), dist(_dist)
{
;
}
int city;
int dist;
bool operator<(const Path& _rhs) const
{
return dist < _rhs.dist;
}
bool operator>(const Path& _rhs) const
{
return dist > _rhs.dist;
}
}; struct City
{
std::vector<Path> paths;
int team;
int dist = std::numeric_limits<int>::max();
bool collected = false;
int team_max = ;
int dist_count = ;
}; #ifdef HEAP
class Comparator
{
public:
Comparator(std::vector<City>& _cities)
: cities_(&_cities)
{
;
}
bool operator()(int _lhs, int _rhs)
{
return (*cities_)[_lhs].dist > (*cities_)[_rhs].dist;
}
private:
std::vector<City>* cities_;
};
#endif int main()
{
int n, m, src, dst;
std::cin >> n >> m >> src >> dst;
std::vector<City> cities(n);
for (auto& city : cities)
std::cin >> city.team;
for (int cnt = ; cnt != m; ++cnt)
{
int src, dst, dist;
std::cin >> src >> dst >> dist;
cities[src].paths.emplace_back(dst, dist);
cities[dst].paths.emplace_back(src, dist);
} {
auto& city = cities[src];
cities[src].collected = true;
cities[src].dist = ;
cities[src].dist_count = ;
cities[src].team_max = cities[src].team;
}
#ifdef QUEUE
std::priority_queue<Path, std::vector<Path>, std::greater<Path>> queue;
#endif
for (const auto& path : cities[src].paths)
{
cities[path.city].dist = path.dist;
cities[path.city].dist_count = ;
cities[path.city].team_max = cities[src].team + cities[path.city].team;
#ifdef QUEUE
queue.emplace(path.city, path.dist);
#endif
} #ifdef HEAP
std::vector<int> heap;
heap.reserve(n - );
for (int i = ; i != n; ++i)
if (i != src)
heap.push_back(i);
Comparator comp(cities);
std::make_heap(heap.begin(), heap.end(), comp);
#endif while ()
{
#ifdef LINEAR
int min_dist = std::numeric_limits<int>::max();
int index = -;
for (int i = ; i != n; ++i)
if (!cities[i].collected && cities[i].dist < min_dist)
min_dist = cities[i].dist, index = i;
if (index == -)
break;
auto& city = cities[index];
#endif
#ifdef HEAP
if (heap.empty())
break;
auto& city = cities[heap[]];
#endif
#ifdef QUEUE
if (queue.empty())
break;
Path temp;
while ()
{
temp = queue.top();
queue.pop();
if (!cities[temp.city].collected)
break;
}
auto& city = cities[temp.city];
#endif
city.collected = true;
for (const auto& path : city.paths)
{
if (!cities[path.city].collected)
{
auto& dest = cities[path.city];
if (city.dist + path.dist < cities[path.city].dist)
{
dest.dist = city.dist + path.dist;
dest.dist_count = city.dist_count;
dest.team_max = city.team_max + dest.team;
}
else if (city.dist + path.dist == cities[path.city].dist)
{
dest.dist = city.dist + path.dist;
dest.dist_count += city.dist_count;
if (city.team_max + dest.team > dest.team_max)
dest.team_max = city.team_max + dest.team;
}
#ifdef QUEUE
queue.emplace(path.city, dest.dist);
#endif
}
}
#ifdef LINEAR
if (index == dst)
break;
#endif
#ifdef HEAP
if (heap[] == dst)
break;
std::pop_heap(heap.begin(), heap.end(), comp);
heap.pop_back();
std::make_heap(heap.begin(), heap.end(), comp);
#endif
#ifdef QUEUE
if (temp.city == dst)
break;
#endif
} {
auto& city = cities[dst];
std::cout << cities[dst].dist_count << ' ' << cities[dst].team_max;
} return ;
}

测试结果:

线性查找版

最小堆版

优先队列版

平台显示线性查找版的时间6ms,内存512KB;最小堆版的时间5ms,内存512KB;优先队列版的时间3ms,内存424KB。我认为时间都太短了,数据量不够大,不足以说明问题。

如果仅从理论上分析的话,我认为优先队列的算法是最优的。

参考资料:

[1] dijkstra + heap 优化 https://blog.csdn.net/sentimental_dog/article/details/51955765

Dijkstra算法与堆(C++)的更多相关文章

  1. dijkstra算法的堆优化

    普通的dijkstra算法模板: //数据结构 int g[LEN][LEN]; //邻接矩阵 int vis[LEN]; //标记是否访问 int dist[LEN] //源点到各点的距离 fill ...

  2. 单源最短路径:Dijkstra算法(堆优化)

    前言:趁着对Dijkstra还有点印象,赶快写一篇笔记. 注意:本文章面向已有Dijkstra算法基础的童鞋. 简介 单源最短路径,在我的理解里就是求从一个源点(起点)到其它点的最短路径的长度. 当然 ...

  3. 单源最短路问题 Dijkstra 算法(朴素+堆)

    选择某一个点开始,每次去找这个点的最短边,然后再从这个开始不断迭代,更新距离. 代码: 朴素(vector存图) #include <iostream> #include <cstd ...

  4. 【Luogu P4779】dijkstra算法的堆优化

    Luogu P4779 利用堆/优先队列快速取得权值最小的点. 在稠密图中的表现比SPFA要优秀. #include<iostream> #include<cstdio> #i ...

  5. 基于STL优先队列和邻接表的dijkstra算法

    首先说下STL优先队列的局限性,那就是只提供入队.出队.取得队首元素的值的功能,而dijkstra算法的堆优化需要能够随机访问队列中某个节点(来更新源点节点的最短距离). 看似可以用vector配合m ...

  6. 最短路径问题---Floyed(弗洛伊德算法),dijkstra算法,SPFA算法

    在NOIP比赛中,如果出图论题最短路径应该是个常考点. 求解最短路径常用的算法有:Floyed算法(O(n^3)的暴力算法,在比赛中大概能过三十分) dijkstra算法 (堆优化之后是O(MlogE ...

  7. 三角网格上的寻路算法Part.1—Dijkstra算法

    背景 最近在研究中产生了这样的需求:在三角网格(Mesh)表示的地形图上给出两个点,求得这两个点之间的地面距离,这条距离又叫做"测地线距离(Geodesic)".计算三角网格模型表 ...

  8. 最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)

    关于几个的区别和联系:http://www.cnblogs.com/zswbky/p/5432353.html d.每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个(草儿家到 ...

  9. Dijkstra算法的二叉堆优化

    Dijkstra算法的二叉堆优化 算法原理 每次扩展一个距离最小的点,再更新与其相邻的点的距离. 如何寻找距离最小的点 普通的Dijkstra算法的思路是直接For i: 1 to n 优化方案是建一 ...

随机推荐

  1. 【Python】wifi开关测试

    #!/usr/bin/python # -*- coding: UTF-8 -*- import os import time def find_device(): os.system('adb ki ...

  2. sqlserver从xlsx读取数据

    exec sp_configure 'show advanced options',1 reconfigure exec sp_configure 'Ad Hoc Distributed Querie ...

  3. iOS-HTTP浅析

    HTTP原理 什么是URL URL中常见的几种协议 什么是HTTP协议 HTTP是做什么的 为什么要使用HTTP协议 HTPP协议的通信过程介绍 HTTP请求 HTTP响应 HTTP请求的选择 两种发 ...

  4. 零元学Expression Blend 4 - Chapter 30 8个Expression Blend4的快捷

    原文:零元学Expression Blend 4 - Chapter 30 8个Expression Blend4的快捷 我针对工作区跟视窗的快捷键整理了八个Expression Blend4的快捷, ...

  5. eval 未将对象引用到对象实例

    1.Eval("No") == null ? "" : Convert.ToString(Eval("NO"))

  6. 漫步Facebook开源C++库Folly之string类设计(散列、字符串、向量、内存分配、位处理等,小部分是对现有标准库和Boost库功能上的补充,大部分都是基于性能的需求而“重新制造轮子”)

    就在近日,Facebook宣布开源了内部使用的C++底层库,总称folly,包括散列.字符串.向量.内存分配.位处理等,以满足大规模高性能的需求. 这里是folly的github地址:https:// ...

  7. 发布Qt Quick桌面应用程序的方法(使得planets在XP上运行)

    发布Qt Quick桌面应用程序的方法 Qt是一款优秀的跨平台开发框架,它可以在桌面.移动平台以及嵌入式平台上运行.目前Qt 5介绍程序发布的文章帖子比较少.大家又非常想要知道如何发布Qt应用程序,于 ...

  8. How Qt Signals and Slots Work(感觉是通过Meta根据名字来调用)

    Qt is well known for its signals and slots mechanism. But how does it work? In this blog post, we wi ...

  9. 【Linux】Linux 环境下splite以及一些正则使用

    由于在windows下,遍历目录,想查找满足条件的文件: dir /s > ..\fileresult.txt 结果得到的文件过大,999多MB的txt: split -b 10k date.f ...

  10. 简单解说Linux命令输出与命令替换

    Linux命令能提高更方便的使用性能.下面就这就来讲术Linux命令.将一个程序或Linux命令的输出作为另一个程序或命令的输入,有两种方法,一种是通过一个临时文件将两个命令或程序结合在一起,例如上个 ...