图论——最短路:Floyd,Dijkstra,Bellman-Ford,SPFA算法及最小环问题
一.Floyd算法
用于计算任意两个节点之间的最短路径。
参考了five20的博客
Floyd算法的基本思想如下:从任意节点A到任意节点B的最短路径不外乎2种可能,1是直接从A到B,2是从A经过若干个节点到B,所以,我们假设dist(AB)为节点A到节点B的最短路径的距离,对于每一个节点K,我们检查dist(AK) + dist(KB) < dist(AB)是否成立,如果成立,证明从A到K再到B的路径比A直接到B的路径短,我们便设置 dist(AB) = dist(AK) + dist(KB),这样一来,当我们遍历完所有节点K,dist(AB)中记录的便是A到B的最短路径的距离。
标准五行代码如下:
- for(k=;k<=n;k++)
- for(i=;i<=n;i++)
- for(j=;j<=n;j++) {
- if(dis[i][k]+dis[k][j]<dis[i][j]) {
- dis[i][j]=dis[i][k]+dis[k][j];
- }
- }
但是这里我们要注意循环的嵌套顺序,如果把检查所有节点K放在最内层,那么结果将是不正确的,为什么呢?因为这样便过早的把i到j的最短路径确定下来了,而当后面存在更短的路径时,已经不再会更新了。
更多关于Floyd算法,详细请见:Floyd算法百度百科链接
二.Dijkstra算法:
适用于权值为非负的图的单源最短路径。
斐波那契堆优化的时间复杂度为O(E+VlogV),但其实只是理论值,实际中基本上达不到。
算法思路:
参考了殷天文的博客
指定一个节点,例如我们要计算 'A' 到其他节点的最短路径
引入两个集合(S、U),S集合包含已求出的最短路径的点(以及相应的最短长度),U集合包含未求出最短路径的点(以及A到该点的路径,注意 如上图所示,
A->C
由于没有直接相连 初始时为∞)初始化两个集合,S集合初始时 只有当前要计算的节点,
A->A = 0
,U集合初始时为
A->B = 4, A->C = ∞, A->D = 2, A->E = ∞
,敲黑板!!!接下来要的两个步骤是核心!从U集合中找出路径最短的点,加入S集合,例如
A->D = 2
更新U集合路径,
if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' )
则更新U循环执行 4、5 两步骤,直至遍历结束,得到A 到其他节点的最短路径
朴素版时间复杂度O(n²)算法代码如下:
- void Dijkstra()
- {
- memset(dis, 0x1f, sizeof(dis));
- dis[] = ;
- for (int i=; i<=n; i++) {
- int min_len = 1e9, k = ;
- for (int j=; j<=n; j++)
- if (!vis[j] && dis[j] < min_len) {
- min_len = dis[j];
- k = j;
- }
- vis[k] = ;
- for (int j=h[k]; j!=-; j=edge[j].next) {
- int to = edge[j].to, w = edge[j].w;
- if (!vis[to] && dis[to] > dis[k]+w) {
- dis[to] = dis[k]+w;
- }
- }
- }
- }
稳定时间复杂度O(mlogn)的堆优化(优先队列代替)代码如下:
- void Dijkstra_Heap()
- {
- priority_queue<pair<int, int> > q;
- memset(dis, 0x3f, sizeof(dis));
- memset(vis,,sizeof(v));
- dis[] = ;
- q.push(make_pair(, ));
- while (!q.empty()) {
- int now = q.top().second;
- q.pop();
- if (vis[now]) continue;
- vis[now] = ;
- for (int i=h[now]; i!=-; i=edge[i].next) {
- int to = edge[i].to, w = edge[i].w;
- if (!vis[to] && dis[to]>dis[now]+w) {
- dis[to] = dis[now] + w;
- q.push(make_pair(-dis[to], to));
- }
- }
- }
- }
这里有一个关于优先队列的小骚操作:
由于STL中的优先队列默认是大根堆,所以在使用push函数的时候只需在需要排序的数据前加个‘-’即可。
关于Dijkstra算法的选择上,对于稀疏图,由于n和m比较接近,故选择堆优化算法;而对于稠密图,由于点少边多,故选择朴素版算法。
更多关于Dijkstra算法,详细请见:Dijkstra算法百度百科链接
三.Bellman-Ford算法
参考博客:图解贝尔曼福特-算法
可用于解决以下问题:
- 从A出发是否存在到达各个节点的路径(有计算出值当然就可以到达);
- 从A出发到达各个节点最短路径(时间最少、或者路径最少等)
- 图中是否存在负环路(权重之和为负数)
- 有边数限制的最短路
算法思路:
- 初始化时将起点s到各个顶点v的距离dist(s->v)赋值为∞,dist(s->s)赋值为0
- 后续进行最多n-1次遍历操作,对所有的边进行松弛操作,假设:
- 所谓的松弛,以边ab为例,若dist(a)代表起点s到达a点所需要花费的总数, dist(b)代表起点s到达b点所需要花费的总数,weight(ab)代表边ab的权重, 若存在:
- (dist(a) +weight(ab)) < dist(b)
- 则说明存在到b的更短的路径,s->...->a->b,更新b点的总花费为(dist(a) +weight(ab)),父节点为a
- 遍历都结束后,若再进行一次遍历,还能得到s到某些节点更短的路径的话,则说明存在负环路
思路上与狄克斯特拉算法(Dijkstra algorithm)最大的不同是每次都是从源点s重新出发进行"松弛"更新操作,而Dijkstra则是从源点出发向外扩逐个处理相邻的节点,不会去重复处理节点,这边也可以看出Dijkstra效率相对更高点。
下面是有边数k限制的Bellman_ford算法模板:
- void Bellman_ford()
- {
- memset(dis,0x1f,sizeof(dis));
- dis[]=;
- for(int i=;i<=k;i++)
- {
- memcpy(last,dis,sizeof(last));
- for(int j=;j<=m;j++)
- {
- int u=edge[j].u,v=edge[j].v,w=edge[j].w;
- if(dis[v]>last[u]+w)dis[v]=last[u]+w;
- }
- }
- }
更多关于Bellman-Ford算法,详细请见:Bellman-Ford算法百度百科链接
四.SPFA算法
SPFA是西安交通大学的段凡丁在1994年与《西安交通大学学报》中发表的“关于最短路径的SPFA快速算法”,他在里面说SPFA速度比Dijkstra快,且运行V次的SPFA速度比Floyd速度快。
而事实证明SPFA算法是有局限的,他不适用于稠密图,对于特别情况的稠密图,SPFA复杂度和BellmanFord时间一样。
适用范围:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).
注意:SPFA算法在网格图中非常慢
算法思路:
参考了小天位的博客
- SPFA(Shortest Path Faster Algorithm) [图的存储方式为邻接表]。
- 是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。
- 算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。
- SPFA 在形式上和BFS非常类似,不同的是BFS中一个点出了队列就不可能重新进入队列,但是SPFA中,一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。
- 判断有无负环:如果某个点进入队列的次数超过V次则存在负环(SPFA无法处理带负环的图)。
代码如下:
- void SPFA() {
- memset(dis, 0x3f, sizeof(dis));
- memset(vis, , sizeof(vis));
- queue<int> q;
- dis[] = ; vis[] = ;
- q.push();
- while (!q.empty()) {
- int now = q.front();
- q.pop(); vis[now] = ;
- for (int i=h[now]; i!=-; i=edge[i].next) {
- int to = edge[i].to, w = edge[i].w;
- if (dis[now]+w < dis[to]) { //在队列中的点也可以进行松弛操作,故这里不需加!ifq[to]的条件,而需要加在下面。
- dis[to] = dis[now]+w;
- if (!vis[to]) q.push(to), vis[to] = ;
- }
- }
- }
- }
更多关于SPFA算法,详细请见:SPFA算法百度百科链接
五.最小环问题
解决思路:
最小环就是指在一张图中找出一个环,使得这个环上的各条边的权值之和最小。在Floyed的同时,可以顺便算出最小环。
记两点间的最短路为dis[i][j],g[i][j]为边<i,j>的权值。
一个环中的最大结点为k(编号最大),与它相连的两个点为i,j,这个环的最短长度为g[i][k]+g[k][j]+(i到j的路径中,所有结点编号都小于k的最短路径长度)。
根据Floyed的原理,在最外层循环做了k-1次之后,dis[i][j]则代表了i到j的路径中,所有结点编号都小于k的最短路径。
综上所述,该算法一定能找到图中最小环。
代码如下:
参考了Coder_YX的博客
- void floyd(){
- int MinCost = inf;
- for(int k=;k<=n;k++){
- for(int i=;i<k;i++)
- for(int j=i+;j<k;j++)
- MinCost = min(MinCost,dis[i][j]+mp[i][k]+mp[k][j]);//更新k点之前枚举ij求经过ijk的最小环
- for(int i=;i<=n;i++)
- for(int j=;j<=n;j++)
- dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); //更新k点
- }
- if(MinCost==inf)puts("It's impossible.");
- else printf("%d\n",MinCost);
- }
关于四种最短路算法的结论:
参考了xiazdong的博客
(1)当权值为非负时,用Dijkstra。
(2)当权值有负值,且没有负圈,则用SPFA,SPFA能检测负圈,但是不能输出负圈。
(3)当权值有负值,而且可能存在负圈,则用BellmanFord,能够检测并输出负圈。
(4)SPFA检测负环:当存在一个点入队大于等于V次,则有负环。
图论——最短路:Floyd,Dijkstra,Bellman-Ford,SPFA算法及最小环问题的更多相关文章
- (最短路径算法整理)dijkstra、floyd、bellman-ford、spfa算法模板的整理与介绍
这一篇博客以一些OJ上的题目为载体.整理一下最短路径算法.会陆续的更新... 一.多源最短路算法--floyd算法 floyd算法主要用于求随意两点间的最短路径.也成最短最短路径问题. 核心代码: / ...
- 最短路(floyd/dijkstra/bellmanford/spaf 模板)
floyd/dijkstra/bellmanford/spaf 模板: 1. floyd(不能处理负权环,时间复杂度为O(n^3), 空间复杂度为O(n^2)) floyd算法的本质是dp,用dp[k ...
- hdu 2066 ( 最短路) Floyd & Dijkstra & Spfa
http://acm.hdu.edu.cn/showproblem.php?pid=2066 今天复习了一下最短路和最小生成树,发现居然闹了个大笑话-----我居然一直写的是Floyd,但我自己一直以 ...
- 图论--最短路--Floyd(含路径输出)
#include<bits/stdc++.h> using namespace std; #define INF 0x3f3f3f3f #define maxn 1005 int D[ma ...
- hdoj 2544 最短路【dijkstra or spfa】
最短路 Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submis ...
- hdoj2544 最短路(Dijkstra || Floyd || SPFA)
题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2544 思路 最短路算法模板题,求解使用的Dijkstra算法.Floyd算法.SPFA算法可以当做求解 ...
- HDOJ 2544 最短路(最短路径 dijkstra算法,SPFA邻接表实现,floyd算法)
最短路 Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submis ...
- hdu1874 畅通project续 最短路 floyd或dijkstra或spfa
Problem Description 某省自从实行了非常多年的畅通project计划后.最终修建了非常多路.只是路多了也不好,每次要从一个城镇到还有一个城镇时,都有很多种道路方案能够选择.而某些方案 ...
- 最短路(Dijkstra,Floyd,Bellman_Ford,SPFA)
当然,这篇文章是借鉴大佬的... 最短路算法大约来说就是有4种——Dijkstra,Floyd,Bellman_Ford,SPFA 接下来,就可以一一看一下... 1.Dijkstra(权值非负,适用 ...
随机推荐
- 日志(log4j2)
日志测试java代码如下: package com.learn.test; import org.apache.logging.log4j.LogManager; import org.apache. ...
- 前端开发 Vue -3axios
Axios是什么? 应该念“阿克希奥斯”……但是太长太拗口,我一般念“阿笑斯”…… Axios 是一个基于 promise 的 HTTP 库,简单的讲就是可以发送get.post请求.说到get.po ...
- 漏洞预警 | ThinkPHP 5.x远程命令执行漏洞
ThinkPHP采用面向对象的开发结构和MVC模式,融合了Struts的思想和TagLib(标签库).RoR的ORM映射和ActiveRecord模式,是一款兼容性高.部署简单的轻量级国产PHP开发框 ...
- 提高前端开发效率的N种方法
一.使用固定的html模板和css公共样式 事先把模板建好,每次需要用的时候直接拿来就行,不再需要为浏览器兼容问题考虑太多时间 这里我整理了一套,希望对大家有帮助:http://www.cnblogs ...
- GIL锁、进程池与线程池、同步异步
GIL锁定义 GIL锁:Global Interpreter Lock 全局解释器 本质上是一把互斥锁 官方解释: 在CPython中,这个全局解释器锁,也称为GIL,是一个互斥锁,防止多个线程在同 ...
- SpringBoot中使用POI,快速实现Excel导入导出
导出Excel 整体来说,Excel有.xls和.xlsx,那么在POI中这两个也对应两个不同的类,但是类名不同,方法基本都是一致的,因此我这里将只介绍.xls一种. 整体来说,可以分为如下七个步骤: ...
- 7.Redis的发布订阅
Redis消息的订阅/发布 a)是什么 进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. b)命令 SUBSCRIBE订阅 PUBLISH发布 c)案例 先订阅后发布后才 ...
- 干货,阿里P8浅谈对java线程池的理解(面试必备)
线程池的概念 线程池由任务队列和工作线程组成,它可以重用线程来避免线程创建的开销,在任务过多时通过排队避免创建过多线程来减少系统资源消耗和竞争,确保任务有序完成:ThreadPoolExecutor ...
- JLINK固件烧写
最近在使用uVision V5.14.0.0 的时候,由于我使用的Jlink是盗版的,导致软件总是退出,然后再网上找到了解决办法. 下面介绍解决办法: 参考: http://www.9mcu.com/ ...
- 用js刷剑指offer(旋转数组的最小数字)
题目描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素. 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个 ...