单源最短路径:Dijkstra算法(堆优化)
前言:趁着对Dijkstra还有点印象,赶快写一篇笔记。
注意:本文章面向已有Dijkstra算法基础的童鞋。
简介
单源最短路径,在我的理解里就是求从一个源点(起点)到其它点的最短路径的长度。
当然,也可以输出这条路径,都不是难事。
但是,Dijkstra不能处理有负权边的图。
解析
注:接下来,我们的源点均默认为1。
先上代码(注意,是堆优化过的!!):
struct node{
int id;
int total;
node(){};
node(int Id,int Total){
id=Id;
total=Total;
}
bool operator < (const node& x) const{
return total>x.total;
}
};
void dijkstra(int start){
memset(dis,inf,sizeof(dis));
memset(conf,false,sizeof(conf));
memset(pre,0,sizeof(pre));
dis[start]=0;
priority_queue <node> Q;
Q.push(node(1,0));
while(Q.size()){
int u=Q.top().id;
Q.pop();
if(conf[u])
continue;
conf[u]=true;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
int cost=dis[u]+e[i].w;
if(cost < dis[v]){
dis[v]=cost;
pre[v]=u;
Q.push(node(v,dis[v]));
}
}
}
}
接下来,一步一步解析代码:
首先是结构体node
struct node{
int id;
int total;
node(){};
node(int Id,int Total){
id=Id;
total=Total;
}
bool operator < (const node& x) const{
return total>x.total;
}
};
这里的id就是这个结点的编号,total就是走到当前节点的最小花费。
构造函数就不用我多说了吧。
因为在原始的Dijkstra中,每次都要选出当前花费最小的那个点,如果采用堆优化,使得堆头永远都是花费最小的那个,这样每次选出花费最小的那个点的时间复杂度从\(O(n)\)骤降到\(O(logn)\)。
如果要用到堆,就可以使用STL的优先队列(priority_queue
)。
因为优先队列默认是优先级最高的放在最前面,在Dijkstra中,优先级就是这个node的total,total越小优先级就越高。
因为total越大,优先级越低,所以这里的小于运算符就可以定义为total>x.total
。
接下来是初始化
memset(dis,inf,sizeof(dis));
memset(conf,false,sizeof(conf));
memset(pre,0,sizeof(pre));
dis[start]=0;
Q.push(node(1,0));
数组dis[i]
表示的是从源点到点i的最短路的长度,初始时不知道能不能到达,设为inf
(无穷大)。
数组conf[i]
表示的是点i的最短路径是否确认,若是,则为true
,否则为false
。
数组pre[i]
表示的是点i的前驱,即到点i的前一个点的编号。
例如有一条最短路径是这样的:1->3->8->5->2
,那么pre[2]=5;pre[5]=8;pre[8]=3;
。
这样一来,输出路径就好办了:
//假设要输出到2的路径
int i=2;
while(pre[i]!=1){
ans.push(i);
i=pre[i];
}
printf("1");
while(!ans.empty()){
printf("->%d",ans.top());
ans.pop();
}
此外,一开始从结点1出发,到结点1的距离为0,知道这些信息后,将源点入堆。
Q.push(node(1/*节点编号*/,0/*到该节点距离*/));
接下来是重点了,我们再次一步步地拆分:
int u=Q.top().id;
Q.pop();
if(conf[u])
continue;
conf[u]=true;
这个应该不难理解,首先拿出一个源点u,u的编号自然是Q.top().id
。接下来Q.pop()
必不可少。
这时候,如果conf[u]==true
,即结点u的最短路长度已经确定过了,那就没必要再走了,因为之前肯定走过了。直接continue
看下一个结点。
如果没有,按照Dijkstra的特性,当前结点u的总路径长度肯定是最短了,那么就被确定了,conf[u]=true
。
然后是下一段:
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
int cost=dis[u]+e[i].w;
if(cost < dis[v]){
dis[v]=cost;
pre[v]=u;
Q.push(node(v,dis[v]));
}
}
这段其实好理解,不过我用的是链式前向星存图,如果你用的是vector做的邻接表,其实大体上是相同的。
如果你用的是邻接表或邻接矩阵,这里的v
其实就是当前找的这条路的终点(e[i].v
表示的是这条边的终点。
而cost
,则是dis[u]
的值加上这条边的权值(没错,e[i].w
表示的是这条边的权值),也就是到点v的总花费。
如果cost<dis[v]
,即当前这条路到v的总花费比原来到v的总花费小,就可以更新了:
dis[v]=cost;
pre[v]=u;
Q.push(node(v,dis[v]));
首先是总花费更新,然后再更新前驱,最后把这个到过的点放入优先队列里。
至此,堆优化Dijkstra就结束了。
但是有一个比较关心的点:时间复杂度是多少呢?
首先考虑有哪些结点会被搜索:
显然是一开始conf[u]==false
的结点,而一点出堆之后,conf[u]=true
,所以有n
个节点会被搜索同时入队,每次入队需要\(O(logn)\)。
接下来是遍历每个结点的边,如果用\(E_i\)表示和结点\(i\)相邻的边的数量,显然有:\(\sum_{i=1}^n E_i = m\),在最坏情况下,每次搜索边的时候都要入队一次,那么总时间复杂度就是:\(O(mlogn)\)。
完结撒花✿
单源最短路径:Dijkstra算法(堆优化)的更多相关文章
- 单源最短路径Dijkstra算法,多源最短路径Floyd算法
1.单源最短路径 (1)无权图的单源最短路径 /*无权单源最短路径*/ void UnWeighted(LGraph Graph, Vertex S) { std::queue<Vertex&g ...
- 单源最短路径——dijkstra算法
dijkstra算法与prim算法的区别 1.先说说prim算法的思想: 众所周知,prim算法是一个最小生成树算法,它运用的是贪心原理(在这里不再证明),设置两个点集合,一个集合为要求的生成树的 ...
- 单源最短路径 dijkstra算法实现
本文记录一下dijkstra算法的实现,图用邻接矩阵表示,假设图为无向图.而且连通,有向图,不连通图的做法相似. 算法简述: 首先确定"单源"的源.假设是第0个顶点. 维护三个数组 ...
- 单源最短路径-Dijkstra算法
1.算法标签 贪心 2.算法描述 具体的算法描述网上有好多,我觉得莫过于直接wiki,只说明一些我之前比较迷惑的. 对于Dijkstra算法,最重要的是维护以下几个数据结构: 顶点集合S : 表示已经 ...
- 单源最短路径——Dijkstra算法学习
每次都以为自己理解了Dijkstra这个算法,但是过没多久又忘记了,这应该是第4.5次重温这个算法了. 这次是看的胡鹏的<地理信息系统>,看完之后突然意识到用数学公式表示算法流程是如此的好 ...
- 最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)
关于几个的区别和联系:http://www.cnblogs.com/zswbky/p/5432353.html d.每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个(草儿家到 ...
- luogu P3371 & P4779 单源最短路径spfa & 最大堆优化Dijkstra算法
P3371 [模板]单源最短路径(弱化版) 题目背景 本题测试数据为随机数据,在考试中可能会出现构造数据让SPFA不通过,如有需要请移步 P4779. 题目描述 如题,给出一个有向图,请输出从某一点出 ...
- [数据结构与算法-15]单源最短路径(Dijkstra+SPFA)
单源最短路径 问题描述 分别求出从起点到其他所有点的最短路径,这次主要介绍两种算法,Dijkstra和SPFA.若无负权优先Dijkstra算法,存在负权选择SPFA算法. Dijkstra算法 非负 ...
- Dijkstra算法堆优化
转自 https://blog.csdn.net/qq_41754350/article/details/83210517 再求单源最短路径时,算法有优劣之分,个人认为在时间方面 朴素dijkstra ...
- matlab练习程序(单源最短路径Dijkstra)
图的相关算法也算是自己的一个软肋了,当年没选修图论也是一大遗憾. 图像处理中,也有使用图论算法作为基础的相关算法,比如图割,这个算法就需要求最大流.最小割.所以熟悉一下图论算法对于图像处理还是很有帮助 ...
随机推荐
- MTK Android Git提取出两个版本之间的差异文件并打包
git提取出两个版本之间的差异文件并打包 首先你得知道版本之间的commit id git log –pretty=oneline $ git log --pretty=oneline 1 差异文件并 ...
- 躁!DJ 风格 Java 桌面音乐播放器
本文适合有 Java 基础知识的人群,跟着本文可学习和运行 Java 版桌面 DJ 音乐播放器. 本文作者:HelloGitHub-秦人 HelloGitHub 推出的<讲解开源项目>系列 ...
- MathJax的基本使用
MathJax是一个开放源代码的JavaScript显示引擎,适用于所有现代浏览器中的LaTeX.MathML和AsciMath表示法. MathJax官网为 https://www.mathjax. ...
- IDEA永久激活码获取
废话少说,直接上地址:http://idea.medeming.com/jet/ *小编还是建议,有家底的程序猿还是直接买正版的号,非正版的很多不便的地方~
- Android调用系统设置
最近,弄了一下,调用系统设置的方法,Android4.0的系统,下面的所有设置项,都亲测可以调用.首先调用的方式如下: Intent mintent_setting_time = new Intent ...
- Go语言讲解深拷贝与浅拷贝
我们在开发中会经常的把一个变量复制给另一个变量,那么这个过程,可能是深浅拷贝,那么今天帮大家区分一下这两个拷贝的区别和具体的区别. 一.概念 1.深拷贝(Deep Copy): 拷贝的是数据本身,创造 ...
- [Php][linux][nginx] 安装总结
就想总结一份安装环境文档,因为文档很多问题也很多,总结一份自己的安装文档! 首先,环境CentOS7,linux环境,windows! 1. 网络配置! vi /etc/sysconfig/netwo ...
- Docker 搭建 ELK 集群步骤
前言 本篇文章主要介绍在两台机器上使用 Docker 搭建 ELK. 正文 环境 CentOS 7.7 系统 Docker version 19.03.8 docker-compose version ...
- TT企业微信社群辅助,企业微信社群辅助工具,允许批量添加好友,自动同意添加请求,自动回复消息
TT企业微信社群辅助,企业微信社群辅助工具,允许批量添加好友,自动同意添加请求,自动回复消息 界面截图 TT企业微信社群辅助工具下载 链接: https://pan.baidu.com/s/1Y2An ...
- session开启慢的原因及解决办法
做微信开发的时候发现微信回复特别慢,发个消息要好几秒才回复,发现不正常后就赶紧找答案,到最后发现是session_start()开启很慢,这是因为session缓存文件过多,默认缓存文件在:win:w ...