当前位置: 首页 > news >正文

数据结构 之 【图的最短路径】(Dijstra、BellmanFord、FloydWarShall算法实现)

目录

1.最短路径

1.1单源最短路径--Dijstra算法

代码实现

完整呈现

1.2打印最短路径的算法

1.3单源最短路径--Bellman-Ford算法

代码实现

完整呈现

1.4多源最短路径--Floyd-Warshall算法

代码实现

完整呈现

2.总结


下述算法均是在邻接表实现图的基础上实现的,参考我的往期博客

1.最短路径

最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小

1.1单源最短路径--Dijstra算法

  • 单源最短路径问题:给定一个图G = ( V , E ),求源结点s ∈ V图 中每个结点v ∈ V 的最短路径
  • 算法思路:将顶点分为S、Q两个集合,S中存放最短路径中的顶点,Q存放尚未确定的顶点。每次从Q中找出一个从源节点到该点代价最小的顶点,将其放入S中,然后对其相邻节点进行松弛操作,反复进行找点、放入、松弛操作,直到所有顶点都进入S为止。至于一些起点到达不了的结点在算法循环后其代价仍为初始设定 的值,不发生变化
  • 松弛操作:( 称找到的顶点为u )将源点s通过该顶点u再到其相邻顶点v的权值和newSum与之前源点s到该相邻顶点v的权值和oldSum进行比较,如果newSum < oldSum 就更新oldSum为newSum 否则oldSum不变

  • 算法要求:要求有向图,且图中所有边的权重非负
  • 算法存在的问题:不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路 径的最短路径
  • Dijkstra算法每次都是选择Q中最小的路径节点来进行更新,并加入S中,所以该算法使用的是贪心策略

代码实现

void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{int n = _vertexes.size();int srci = GetIndexOfVertexes(src);dist.resize(n, W_MAX);pPath.resize(n, -1);dist[srci] = W();//最短路径长度pPath[srci] = srci;//父亲节点//分为两个集合S、Q,从Q中寻找代价最小的顶点u,然后进行松弛操作vector<bool> in(n, false);//从Q中选顶点
}

(1)顶点个数n、源点下标srci

(2)初始化源点到其他顶点的权值和为权值类型的最大值,

各个顶点的父亲节点下标初始化为-1

(3)使用bool数组区别S、Q中的顶点:

顶点在S中,顶点下标对应值为true

//一次选一个顶点,选n次
for(int k = 0; k < n; ++k)
{//找到了代价最小的顶点uW min = W_MAX;int u = srci;bool flag = false;for (int i = 0; i < n; ++i){	//在Q中if (in[i] == false && dist[i] < min){flag = true;min = dist[i];u = i;}}if (flag == false){reutrn;}in[u] = true;//找到了就进入S//向外进行松弛操作 srci -> u u ->v 与 srci -> vfor (int v = 0; v < n; ++v){if (_matrix[u][v] < 0) throw invalid_argument("无效参数");//v点在S中,dist[u] + _matrix[u][v] >= dist[v]if (in[v] == false && _matrix[u][v] != W_MAX &&dist[u] + _matrix[u][v] < dist[v]){dist[v] = dist[u] + _matrix[u][v];pPath[v] = u;}}
}

(1)最外层for循环表示(找点、进集合、松弛)操作的次数,图中有n个顶点,就进行n次

使得源点能够到达包括自己在内的所有顶点

(2)找点:这里没有用优先级队列,而是在dist数组中暴力查找Q中代价最小的顶点

使用 flag 作为标记,当不能从Q中找出代价最小的顶点时,说明源点已到达它所能到达的所有顶点了

(3)标记:in[u] = true;//找到了就进入S

(4)松弛操作:srci -> u u ->v 与 srci -> v两者的值进行比较更新

  • 存在负权直接抛异常
  • 如果不判断u的临近顶点(u可直接到达)v是否在S中,程序也能实现该算法。这是因为:如果v在S中,则dist[u] >= dist[v](v是找点时代价最小的顶点),_matrix[u][v] >= 0,所以dist[u] + _matrix[u][v] >= dist[v],dist[v]不更新    如果v不在S中,正常比较更新即可

(5)如果更新了,父亲节点下标就是u

完整呈现

void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{int n = _vertexes.size();int srci = GetIndexOfVertexes(src);dist.resize(n, W_MAX);pPath.resize(n, -1);dist[srci] = W();//最短路径长度pPath[srci] = srci;//父亲节点//分为两个集合S、Q,从Q中寻找代价最小的顶点u,然后进行松弛操作vector<bool> in(n, false);//一次选一个顶点,选n次for(int k = 0; k < n; ++k){//找到了代价最小的顶点uW min = W_MAX;int u = srci;bool flag = false;for (int i = 0; i < n; ++i){	//在Q中if (in[i] == false && dist[i] < min){flag = true;min = dist[i];u = i;}}if (flag == false){cout << "整个图不是连通图" << endl;}in[u] = true;//找到了就进入S//向外进行松弛操作 srci -> u u ->v 与 srci -> vfor (int v = 0; v < n; ++v){if (_matrix[u][v] < 0) throw invalid_argument("无效参数");//v点在S中,dist[u] + _matrix[u][v] >= dist[v]if (in[v] == false && _matrix[u][v] != W_MAX &&dist[u] + _matrix[u][v] < dist[v]){dist[v] = dist[u] + _matrix[u][v];pPath[v] = u;}}}
}

1.2打印最短路径的算法

void PrintShortPath(const V& src, vector<W>& dist, vector<int>& pPath)
{int n = _vertexes.size();int srci = GetIndexOfVertexes(src);//遍历最短路径权值和数组的每个顶点,并打印其父亲节点for (int i = 0; i < n; ++i){if (i == srci) continue;vector<int> path;path.push_back(i);int parenti = pPath[i];while (parenti != srci)//源点和源点连接的第一个顶点符合要求{path.push_back(parenti);parenti = pPath[parenti];}path.push_back(srci);//逆置一下reverse(path.begin(), path.end());for (auto e : path){cout << _vertexes[e] << "->";}cout << dist[i] << endl;}
}

(1)得到顶点个数n、源点下标srci

(2)遍历dist数组,按数组顺序打印最短路径

(3)不打印源点到源点的路径

(4)path数组存放路径,因为pPath存放的是节点的父亲节点下标所以我们是逆着寻找路径

即a->b->c->d->e,我们是逆着往回走,e<-d<-c<-b<-a

寻找逻辑与并查集的查找逻辑类似,向上寻找

  • 首先 path 中存入当前点的下标 i ,然后判断当前点 i 对应的pPath数组元素是否源点下标,如果不是,数组元素进入path,迭代父亲节点下标,继续寻找,循环跳出后,当前节点是源点到达的第一个顶点,再将源点下标压入path,然后逆置一下数组元素,数组中存放的就是源点到该顶点的最短路径了

(5)打印最短路径及权值

1.3单源最短路径--Bellman-Ford算法

  • 算法思路:对于有n个顶点的有向图,进行n-1轮松弛操作。第K轮松弛操作:更新所有通过 k条边 从源点到达的节点的最短距离
  • 为什么是n-1轮松弛操作呢?    对于有n个顶点的有向图,两点之间最多有n-1条边,n-1轮松弛操作就包含了两点之间的所有路径。
  • 负权环:某个回路的权值和为负数
  • 该算法可以检测负权环问题,对于有n个顶点的有向图,第n轮松弛操作如果有更新,那么图中就存在负权环。   这是因为对于有n个顶点的有向图,两点之间最多有n-1条边,如果经过n条边 还可到达,只能说明两点之间存在环

  • BellmanFord就是暴力查找更新,将源点到其他顶点的所有路径都列举比较,但效率就下降了。时间复杂度 O(N^3) 优化策略这里就略过了

代码实现

bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
{int n = _vertexes.size();int srci = GetIndexOfVertexes(src);dist.resize(n, W_MAX);pPath.resize(n, -1);dist[srci] = W();pPath[srci] = srci;//松弛更新
}

(1)顶点个数n、源点下标srci

(2)更新dist、pPath数组,与Dijstra算法实现一致

for(int k = 0; k < n - 1; ++k)
{bool update = false;//从当前最短路径顶点松弛操作相邻顶点for (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){//源点不能到i,直接跳出循环if (dist[i] == W_MAX) break;//srci -> i i -> j srci -> jif (_matrix[i][j] != W_MAX && dist[i] + _matrix[i][j] < dist[j]){update = true;dist[j] = dist[i] + _matrix[i][j];pPath[j] = i;}}}if (update == false) return true;
}

(1)最外层循环表示进行 n-1 轮松弛更新

(2) 使用邻接矩阵,遍历存在的边(使用邻接表效率更高)

如果源点到顶点 i 的最短路径存在,并且 i 、j 相连有边,比较更新

(3)update变量作为标志位,n-1轮松弛更新中,有一次没有进行更新操作就说明

所有最短路径都已找到

			//第n轮松弛更新了,则存在环for (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){if (dist[i] == W_MAX) break;//srci -> i i -> j srci -> jif (_matrix[i][j] != W_MAX && dist[i] + _matrix[i][j] < dist[j]){return false;dist[j] = dist[i] + _matrix[i][j];pPath[j] = i;}}}

完整呈现

bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
{int n = _vertexes.size();int srci = GetIndexOfVertexes(src);dist.resize(n, W_MAX);pPath.resize(n, -1);dist[srci] = W();pPath[srci] = srci;//一次松弛操作最多更新一个长度,n个顶点最多需要n-1次操作//更新所有通过 k条边 从源点到达的节点的最短距离for(int k = 0; k < n - 1; ++k){bool update = false;//从当前最短路径顶点松弛操作相邻顶点for (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){if (dist[i] == W_MAX) break;//srci -> i i -> j srci -> jif (_matrix[i][j] != W_MAX && dist[i] + _matrix[i][j] < dist[j]){update = true;dist[j] = dist[i] + _matrix[i][j];pPath[j] = i;}}}if (update == false) return true;}//第n轮松弛更新,则存在环for (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){if (dist[i] == W_MAX) break;//srci -> i i -> j srci -> jif (_matrix[i][j] != W_MAX && dist[i] + _matrix[i][j] < dist[j]){return false;dist[j] = dist[i] + _matrix[i][j];pPath[j] = i;}}}//不更新,不存在环return true;
}

1.4多源最短路径--Floyd-Warshall算法

  • 该算法解决的是任意两点间的最短路径
  • 算法思路:任一两点之间要么直接相连,要么间接相连,所以通过三重循环(k, i, j),逐步考虑中间顶点k,更新所有顶点对(i,j)的距离

代码实现

bool FloydWarShall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
{int n = _vertexes.size();vvDist.resize(n, vector<W>(n, W_MAX));vvpPath.resize(n, vector<int>(n, -1));//初始化通过一条边直接相连接的顶点for (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){if (_matrix[i][j] != W_MAX){vvDist[i][j] = _matrix[i][j];vvpPath[i][j] = i;}if (i == j){vvDist[i][j] = W();//防止溢出最大值+一个不是最大值的距离vvpPath[i][j] = i;}}}//遍历每个顶点
}

(1)得到顶点个数n

(2)vvDist、vvpPath都是二维数组

(3)根据直接相连的边初始化vvDist、vvpPath,

源点到源点,源点通过一条边直接到达的顶点

//i -> k k -> j 与 i -> j
//中间顶点k
for (int k = 0; k < n; ++k)
{//每对顶点i、jfor (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){if (vvDist[i][k] != W_MAX && vvDist[k][j] != W_MAX&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j]){vvDist[i][j] = vvDist[i][k] + vvDist[k][j];vvpPath[i][j] = vvpPath[k][j];}}}	
}

(1)通过三重循环(k, i, j),逐步考虑中间顶点k(外层循环),

更新所有顶点对(i,j)(内层循环)的距离

(2)vvpPath[i][j]的父亲节点是vvpPath[k][j],这是因为 i -> k k -> j 中过程中 k 与 j 不一定是直接相连vvpPath[k][j]存放的就是 k到j 路径中的倒数第二个顶点的下标

			for (int i = 0; i < n; ++i){if (vvDist[i][i] < 0)return false;}

如果图中存在负权环,那么经过三重循环后,某点到自己的权值和小于0

据此判断负权环

完整呈现

		bool FloydWarShall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath){int n = _vertexes.size();vvDist.resize(n, vector<W>(n, W_MAX));vvpPath.resize(n, vector<int>(n, -1));//初始化通过一条边直接相连接的顶点for (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){if (_matrix[i][j] != W_MAX){vvDist[i][j] = _matrix[i][j];vvpPath[i][j] = i;}if (i == j){vvDist[i][j] = W();//防止溢出最大值+一个不是最大值的距离vvpPath[i][j] = i;}}}//i -> k k -> j 与 i -> j//中间顶点kfor (int k = 0; k < n; ++k){//每对顶点i、jfor (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j){if (vvDist[i][k] != W_MAX && vvDist[k][j] != W_MAX&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j]){vvDist[i][j] = vvDist[i][k] + vvDist[k][j];vvpPath[i][j] = vvpPath[k][j];}}}	}for (int i = 0; i < n; ++i){if (vvDist[i][i] < 0)return false;}return true;}

2.总结

算法时间复杂度空间复杂度适用场景
DijkstraO(V²) → O((V+E)logV)O(V²)非负权图,稀疏图优先堆优化
BellmanO(V³) → O(VE)O(V²)含负权边单源问题,邻接表优化
FloydO(V³)O(V²)多源最短路径,小规模图

http://www.dtcms.com/a/436039.html

相关文章:

  • 时序数据库高基数问题(一):当数据标签太多时会发生什么
  • 东莞市企业网站制作企业关键词推广优化排名品牌
  • 个人网站免费搭建软文标题和内容
  • 普洱高端网站建设价格燕郊房价2023年最新房价走势
  • 怎么做二维码微信扫后直到网站合肥网站排名提升
  • 如何办网站 论坛网站一定要公司吗
  • 主流网站开发平台新媒体运营需要哪些技能
  • 江门网站php网站开发遇到的问题
  • 做韦恩图网站建网站如果不买域名别人能不能访问
  • 如何让订阅号菜单做微网站安徽鲁班建设集团网站
  • 51——DS18B20
  • 网站建设安全协议wordpress 添加插件
  • 区总工会加强网站意识形态建设深圳网络科技有限公司简介
  • 【HTB】Season9 Imagery Walkthrough
  • 异常与c++11中的noexcept【c++】
  • 免费网站制作效果重庆建设施工安全信息网官网
  • 网站设计报价是多少钱网站建设项目计划书
  • Python语言中的应用程序接口(API):本质探析、高级应用与实践范式
  • 小学生做网站下载谷歌浏览器
  • 淄博网站制作高端营销手表网站妨水
  • Spring Boot 常用注解分类整理(含用法示例)
  • 运动网站设计江西商城网站建设
  • 如何做关于网站推广的培训公众号软文怎么写
  • Spring Boot 配置详解:从引导器到注解实战(初学者指南)
  • 网站建设的结论和体会苏州高端建站公司
  • 珠海建设局网站查公司业绩专业h5网站建设教程
  • 136、【OS】【Nuttx】【周边】效果呈现方案解析:strace 日志解析(二)
  • 呼市做网站建设的公司哪家好一个网站成本
  • 洛阳制作网站公司吗wordpress设置内存
  • wordpress vip会员主题企业seo顾问服务公司