最短路径问题

  1. 单源最短路径问题:Dijstra[不带负权边]\Bell_Ford(SPFA)[可带负权边]
  2. 多源最短路径问题:Floy

Dijstra算法:中介点优化

解决不带负权边的单源最短路问题

通过与源点s最短路径已知的点Vi为中介,优化Vi的邻接点与源点s的距离

基本步骤

MAXV:最大顶点数,源点s

数组d[MAXV]:各个顶点到源点s的距离。初始化为INF(无穷大,一般设为100,000,000),d[s]初始化为0。

数组vis[MAXV]:标记数组。 vis[Vi] == true 表示Vi结点已被访问过

  1. 取出未被标记过的顶点中与源点距离最短的顶点Vi,标记Vi
  2. 以Vi为中介点,更新Vi的邻接点到源点s的距离
  3. 将1.2循环n次(n为顶点数)

伪代码

  1. //G为图,一般设成全局变量;数组d为源点到达各点的最短路径长度,s为起点
  2. Dijstra(G, d[], s){
  3. 初始化;
  4. for(循环n次){
  5. u = 使d[u]最小的还未被访问的顶点的标号;
  6. u已被访问;
  7. for(从u出发能到达的所有顶点v){
  8. if(v未被访问 && u为中介点使s到顶点v的最短距离d[v]更优){
  9. 优化d[v];
  10. }
  11. }
  12. }
  13. }

在实现过程中的关键问题

  1. “u = 使d[u]最小的还未被访问的顶点的标号;”————>如何找到d[u]最小值

    遍历d:O(MAXV)

    将d构建为Min_Heap:O(logMAXV)
  2. "v未被访问 && 以u为中介点使s到顶点v的最短距离d[v]更优"————>前者用标记数组vis解决;后者即"d[u] + G[u][v] < d[v]"(邻接矩阵实现时)

代码实现

邻接矩阵版

  1. #include<stdio.h>
  2. #include<algorithm>
  3. using namespace std;
  4. #define MAXV 100 //最大顶点数
  5. #define INF 100000000 //一个很大的数
  6. int n, G[MAXV][MAXV]; //n为顶点数
  7. int d[MAXV]; //起点到各点的最短路径长度
  8. bool vis[MAXV] = {false};//标记数组,vis[i] == false 表示已访问。初值均为false
  9. void Dijstra(int s){ //s为起点
  10. fill(d, d+MAXV*MAXV, INF); //fill函数将整个d数组赋为INF(慎用memset)
  11. d[s] = 0; //起点s到自身的距离为0
  12. for(int i = 0; i < n; i++){ //循环n次
  13. int u = -1, MIN = INF; //u是d[u]最小,MIN存放最小的d[u]
  14. for(int j = 0; j < n; j++){ //找到未访问的顶点中d[u]最小的,时间复杂度为O(MAXV)
  15. if(vis[j] == false && d[j] < MIN){
  16. u = j;
  17. MIN = d[j];
  18. }
  19. }
  20. //找不到小于INF的 d[u], 说明剩下的顶点和起点s不连通
  21. if(u == -1) return ;
  22. vis[u] = true; //标记u为已访问
  23. for(int v = 0; v < n; v++){
  24. //如果v未访问 && u能到达v && 以u为中介点可以使d[v]更优
  25. if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]){
  26. d[v] = d[u] + G[u][v];
  27. }
  28. }
  29. }
  30. }

邻接表版

  1. #include<stdio.h>
  2. #include<algorithm>
  3. #include<vector>
  4. using namespace std;
  5. #define MAXV 100 //最大顶点数
  6. #define INF 100000000 //一个很大的数
  7. struct Node
  8. {
  9. int v, dis; //v表示目标顶点, dis表示边权
  10. };
  11. vector<Node> Adj[MAXV];
  12. int n; //n为顶点数
  13. int d[MAXV]; //起点到各点的最短路径长度
  14. bool vis[MAXV] = {false};//标记数组,vis[i] == false 表示已访问。初值均为false
  15. void Dijstra(int s){ //s为起点
  16. fill(d, d+MAXV*MAXV, INF); //fill函数将整个d数组赋为INF(慎用memset)
  17. d[s] = 0; //起点s到自身的距离为0
  18. for(int i = 0; i < n; i++){ //循环n次
  19. int u = -1, MIN = INF; //u是d[u]最小,MIN存放最小的d[u]
  20. for(int j = 0; j < n; j++){ //找到未访问的顶点中d[u]最小的
  21. if(vis[j] == false && d[j] < MIN){
  22. u = j;
  23. MIN = d[j];
  24. }
  25. }
  26. //找不到小于INF的 d[u], 说明剩下的顶点和起点s不连通
  27. if(u == -1) return ;
  28. vis[u] = true; //标记u为已访问
  29. /**********************只有下面这个与邻接矩阵不同**************************/
  30. for(int i = 0; i < Adj[u].size(); i++){
  31. int v = Adj[u][i].v; //通过邻接表直接获得u能到达的顶点v
  32. if(d[u] + Adj[u][v].dis < d[v]){
  33. d[v] = d[u] + Adj[u][v].dis; //优化d[v]
  34. }
  35. }
  36. }
  37. }

时间复杂度:O(VlogV+E)

外循环:O(V)无法避免

影响因素:

  1. d[MAXV]最小值的获取方式

    遍历d:O(V)

    最小堆:O(logV)
  2. 图G的实现方式

    邻接矩阵:内循环O(V)

    邻接表:与外循环共同作用等价于遍历了所有边,故内外循环的总时间复杂度为O(E)

总结:

邻接表 邻接矩阵
遍历d O(V* V+E) O(V* (V+V))
最小堆 O(V* logV+E) O(V*(logV+V))

算法存在的问题:存在负权边时会失效

一个简单的例子:



在该例中,如果以A顶点为源点,那么A先被标。而后由于 A到B的距离-1 < A到C的距离1,故B到源点的最短距离更新为1,B被标记。

由于被标记过的顶点的距离值不会再被更新,所以根据Dijstra算法得出B到源点A的最短距离为-1,但实际上应该为-4,可见Dijstra算法在存在负权边的图中就出错了。

Bell_Ford和SPFA算法:遍历边优化

解决单源最短路问题,可检测负权边

存在负权边时函数返回false, 不存在时返回true且d[]存放源点到各顶点的最短路径长

Dijstra算法相比:Dijstra主要操作对象是结点,无法处理负权边;BF主要操作对象是边,可以通过一轮循环判断是否存在负权边

基本步骤

构建d[]数组

  1. 对边执行n-1轮操作
  2. 每轮操作遍历所有边
  3. 对每条边u->v,如果以u为中介使得d[v]更小,则更新d[v] 【边松弛】

寻找负权环

再对所有边执行一轮操作,判断是否仍有边满足 d[u]+G[u][v] < d[v]

有则说明存在负权边,无则说明不存在,d数组中所有值已达最优

伪代码

  1. //构建d[]数组
  2. for(int i = 0; i < n-1; i++){ //执行n-1轮操作,其中n为顶点数
  3. for(each edge u->v){ //每轮操作都遍历所有边
  4. if(d[u] + length[u->v] < d[v]) //以u为中介点可以使得d[v]更小
  5. d[v] = d[u] + length[u->v]; //松弛操作
  6. }
  7. }
  8. //检查负环
  9. for(each edge u->v){ //对每条边进行判断
  10. if(d[u] + length[u->v] < d[v]){ //如果仍可以被松弛
  11. return false; //说明图中有从原点可达的负环
  12. }
  13. return true; //数组d的所有值已达最优
  14. }

代码本质:构建最短路径树

  • 最短路径树:如果将从源点s到其他各点的最短路径连接起来,必然会构成一棵树(整体连通&&无环),该树称为最短路径树【别和最小生成树搞混了】。原图和源点一旦确定,最短路径树就唯一确定了

  • BF算法的每一轮循环实际上就是在确定树的一层,树的层数不超过V-1,故循环V-1次

代码实现

  1. /*Bellman_Ford:可处理带有负权环的单源最短路径问题 O(V^2)*/
  2. #include<stdio.h>
  3. #include<vector>
  4. #include<algorithm>
  5. using namespace std;
  6. #define MAXV 100
  7. #define INF 100000000
  8. struct Node{
  9. int v, dis;
  10. };
  11. vector<Node> Adj[MAXV];
  12. int n;
  13. int d[MAXV];
  14. bool Bellman(int s){ //s为源点
  15. fill(d, d+MAXV, INF);
  16. d[s] = 0;
  17. for(int i = 0; i < n-1; i++){ //执行n-1 轮操作,n为顶点数
  18. for(int u = 0; u < n; u++){ //每轮操作都遍历所有边
  19. for(int j = 0; j < Adj[u].size(); j++){
  20. int v = Adj[u][j].v; //邻接边的顶点
  21. int dis = Adj[u][j].dis;//邻接边的边权
  22. if(d[u] + dis < d[v]){ //以u为中介点可以使d[v]更小
  23. d[v] = d[u] + dis;
  24. }
  25. }
  26. }
  27. }
  28. for(int u = 0; u < n; u++){ //对每条边进行判断
  29. for(int j = 0; j < Adj[u].size(); j++){
  30. int v = Adj[u][j].v;
  31. int dis = Adj[u][j].dis;
  32. if(d[u] + dis < d[v]){
  33. return false; //说明图中有从源点可达的负权环
  34. }
  35. }
  36. }
  37. return true;
  38. }

复杂度分析:O(VE)

外循环:执行V-1次,无法改变

内循环(遍历每条边):执行E次(邻接表),执行V^2(邻接矩阵)

邻接表 邻接矩阵
时间复杂度 O(VE) O(V^3)

优化:SPFA(Shortest Path Faster Algorithm)

切入点: 在BF算法中,当某个顶点u的d[u]值改变时,从它出发的边的邻接点v的d[v]值才有可能改变,此时无需遍历所有边。

优化: 建立一个队列,每次将队首元素u取出,对u的所有邻接边u->v进行松弛操作,如果某个顶点v的d[v]改变了,就将v加入队列中。

循环直到 队列为空||某元素v入队次数大于n-1 退出循环。此时,队列为空,说明d数组已经最优,否则,说明存在负权边。

注:入队元素最大入队次数为n-1次: 顶点v最多与n-2个结点连通,如果每次边松弛时v都入队,则入队次数为n-2,如果v由恰好是源点,在初始化时入队1次,则总共入队n-1次。

伪代码

  1. queue<int> q;
  2. 源点s入队;
  3. while(队列非空){
  4. 取出队首元素u;
  5. for(u的所有邻接边u->v){
  6. if(d[u] + G[u][v] < d[v]){
  7. if(v不在队列){
  8. v入队;
  9. if(v入队次数大于n-1){
  10. 说明有可达负环; return;
  11. }
  12. }
  13. }
  14. }
  15. }

队列用stl的queue实现

v入队次数用数组num[MAXV]记录。(MAXV:最大顶点数)

代码实现:邻接表为例

  1. /*基于Bellman-Ford的优化*/
  2. /*无需遍历所有边,而只需遍历u的发出边(基于BFS思想) O(kE)*/
  3. #include<stdio.h>
  4. #include<vector>
  5. #include<queue>
  6. #include<algorithm>
  7. using namespace std;
  8. #define MAXV 100
  9. #define INF 100000000
  10. struct Node{
  11. int v, dis;
  12. };
  13. vector<Node> Adj[MAXV];
  14. int n, d[MAXV], num[MAXV]; //num数组记录入队次数
  15. bool inq[MAXV]; //顶点是否在队列中
  16. bool SPFA(int s){
  17. //初始化部分
  18. fill(inq, inq+MAXV, false);
  19. fill(num, num+MAXV, 0);
  20. fill(d, d+MAXV, INF);
  21. //源点入队部分
  22. queue<int> Q;
  23. Q.push(s);
  24. inq[s] = true; //源点已入队
  25. num[s]++; //源点入队次数+1
  26. d[0] = 0; //源点的d值为0
  27. //主体部分
  28. while(!Q.empty()){
  29. int u = Q.front(); //队首顶点编号为u
  30. Q.pop(); //出队
  31. inq[u] = false; //设置u不在队列中
  32. //遍历u所有的邻接边
  33. for(int j = 0; j < Adj[u].size(); j++){
  34. int v = Adj[u][j].v;
  35. int dis = Adj[u][j].dis;
  36. //松弛操作
  37. if(d[u] + dis < d[v]){
  38. d[v] = d[u] + dis;
  39. if(!inq[v]){ //如果v不在队列中
  40. Q.push(v); //v入队
  41. inq[v] = true; //设置v在队列中
  42. num[v]++; //v的入队次数+1
  43. if(num[v] >= n) return false; //有可达负环
  44. }
  45. }
  46. }
  47. }
  48. return true; //无可达负环
  49. }

复杂度分析:O(kE)

证略。k是一个常数,一般不超过2。

  • 经常优于堆优化的Dijstra算法
  • 存在负权环时退化为O(VE)

Floy算法:待添加

<数据结构>图的最短路径问题的更多相关文章

  1. 数据结构 -- 图的最短路径 Java版

    作者版权所有,转载请注明出处,多谢.http://www.cnblogs.com/Henvealf/p/5574455.html 上一篇介绍了有关图的表示和遍历实现.数据结构 -- 简单图的实现与遍历 ...

  2. [从今天开始修炼数据结构]图的最短路径 —— 迪杰斯特拉算法和弗洛伊德算法的详解与Java实现

    在网图和非网图中,最短路径的含义不同.非网图中边上没有权值,所谓的最短路径,其实就是两顶点之间经过的边数最少的路径:而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,我们称路径上第 ...

  3. <数据结构>图的最小生成树

    目录 最小生成树问题 Prim算法:点贪心 基本思想:类Dijstra 伪代码 代码实现 复杂度分析:O(VlogV + E) kruskal算法:边贪心 基本思想: 充分利用MST性质 伪代码 代码 ...

  4. Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例

    本文实例讲述了Python数据结构与算法之图的最短路径(Dijkstra算法).分享给大家供大家参考,具体如下: # coding:utf-8 # Dijkstra算法--通过边实现松弛 # 指定一个 ...

  5. 数据结构-图-Java实现:有向图 图存储(邻接矩阵),最小生成树,广度深度遍历,图的连通性,最短路径1

    import java.util.ArrayList; import java.util.List; // 模块E public class AdjMatrixGraph<E> { pro ...

  6. 数据结构(C#):图的最短路径问题、(Dijkstra算法)

    今天曾洋老师教了有关于图的最短路径问题,现在对例子进行一个自己的理解和整理: 题目: 要求:变成计算出给出结点V1到结点V8的最短路径 答: 首先呢,我会先通过图先把从V1到V8的各种路径全部计算下来 ...

  7. 带权图的最短路径算法(Dijkstra)实现

    一,介绍 本文实现带权图的最短路径算法.给定图中一个顶点,求解该顶点到图中所有其他顶点的最短路径 以及 最短路径的长度.在决定写这篇文章之前,在网上找了很多关于Dijkstra算法实现,但大部分是不带 ...

  8. 数据结构--图 的JAVA实现(上)

    1,摘要: 本系列文章主要学习如何使用JAVA语言以邻接表的方式实现了数据结构---图(Graph),这是第一篇文章,学习如何用JAVA来表示图的顶点.从数据的表示方法来说,有二种表示图的方式:一种是 ...

  9. 数据结构--图 的JAVA实现(下)

    在上一篇文章中记录了如何实现图的邻接表.本文借助上一篇文章实现的邻接表来表示一个有向无环图. 1,概述 图的实现与邻接表的实现最大的不同就是,图的实现需要定义一个数据结构来存储所有的顶点以及能够对图进 ...

随机推荐

  1. Vue相关,Vue生命周期及对应的行为

    先来一张经典图 生命钩子函数 使用vue的朋友们知道,生命周期函数长这样- mounted: function() { } // 或者 mounted() { } 注意点,Vue的所有生命周期函数都是 ...

  2. [php代码审计] Typecho 1.1 -反序列化Cookie数据进行前台Getshell

    环境搭建 源码下载:https://github.com/typecho/typecho/archive/v1.1-15.5.12-beta.zip 下载后部署到web根目录,然后进行安装即可,其中注 ...

  3. Oracle中的索引

    1.Oracle 索引简介      在Oracle数据库中,存储的每一行数据都有一个rowID来标识.当Oracle中存储着大量的数据时,意味着有大量的rowID,此时想要快速定位指定的rowID, ...

  4. Linux学习 - 文本编辑器Vim

    一.Vim工作模式 二.命令 插入 a 光标后插入 A 光标所在行尾插入 i 光标前插入 I 光标所在行首插入 o 光标下插入新行 O 光标上插入新行   删除 x 删除光标处字符 nx 删除光标处后 ...

  5. dom4j解析XML学习

    原理:把dom与SAX进行了封装 优点:JDOM的一个智能分支.扩充了其灵活性增加了一些额外的功能. package com.dom4j.xml; import java.io.FileNotFoun ...

  6. SSM和springboot对比

    今天在开源中国上看到一篇讲SSM.SpringBoot讲的不错的回答,分享! https://www.oschina.net/question/930697_2273593 一.SSM优缺点应该分开来 ...

  7. Non-terminating decimal expansion; no exact representable decimal result.

    Non-terminating decimal expansion; no exact representable decimal result.  翻译为:非终止十进制扩展; 没有确切的可表示的小数 ...

  8. 【Spark】【复习】Spark入门考前概念相关题复习

    Spark考前概念相关题复习 AUthor:萌狼蓝天 哔哩哔哩:萌狼蓝天 博客园:我的文章 - 萌狼蓝天 博客:萌狼工作室 - 萌狼蓝天 (mllt.cc) 选择题 Hadoop 1.HADOOP的三 ...

  9. Mysql一致性效验_pt工具

    目录 一.简介 二.原理介绍 三.选项 四.环境 五.部署 一.简介 pt工具可以随机抽取主从的数据进行对比,用于测试主从数据一致性.也可以对不一致数据进行修复.这个工具在主或者从上安装均可 二.原理 ...

  10. how2heap学习(二)

    拖了好久,但是在期间做了几道pwn题目,发现堆原来也没有想象中的难. fastbin_dup_into_stack 这个说白了,就是利用double free可以进行任意地址的写,说是任意地址不准确, ...