【C++】深入浅出“图”——最短路径算法

【C++】深入浅出“图”——最短路径算法

文章目录

一、Dijkstra算法

最短路径问题是指,从在带权的有向图中从某一顶点出发,找到通往另一顶点的最短路径,“最短”指的是沿路径各边的权值总和最小。

Dijkstra算法是单源最短路径的经典贪心算法,只能用于没有负权的图。它从起点出发,每次选当前距离最小且未确定最短路径的节点,用它去松弛(更新)所有邻接点的最短路径估计值,标记该节点为 “已确定”,重复此过程直到所有节点处理完毕,最终得到起点到图中所有节点的最短路径。

在这里插入图片描述
// src是选定的起点,dist记录起点到各点的最短路径,pPath记录到每个点的最短路径的前驱顶点下标voidDijkstra(const V& src, vector<W>& dist, vector<int>& pPath){ size_t srci =GetVertexIndex(src); size_t n = _vertexs.size(); dist.resize(n, MAX_W); pPath.resize(n,-1); dist[srci]=0; pPath[srci]= srci;// 已经确定最短路径的顶点集合 vector<bool>S(n,false);for(size_t j =0; j < n;++j){// 选最短路径顶点且不在S更新其他路径int u =0; W min = MAX_W;for(size_t i =0; i < n;++i){if(S[i]==false&& dist[i]< min){ u = i; min = dist[i];}} S[u]=true;// 松弛更新u连接顶点v srci->u + u->v < srci->v 更新for(size_t v =0; v < n;++v){if(S[v]==false&& _matrix[u][v]!= MAX_W && dist[u]+ _matrix[u][v]< dist[v]){ dist[v]= dist[u]+ _matrix[u][v]; pPath[v]= u;}}}}

二、Bellman_Ford算法

Bellman_Ford算法能用来解决负权图的单源最短路径问题,但是它的时间复杂度高于Dijkstra算法,本质是暴力求解。从起点出发,把图里所有边从头到尾松弛一遍,重复n次,就能算出起点到所有点的最短路径;因为任何最短路径最多只经过n‑1条边。跑完之后再扫一遍所有边,如果还能更新距离,就说明图里有负权回路,最短路径不存在。

在这里插入图片描述
boolBellmanFord(const V& src, vector<W>& dist, vector<int>& pPath){ size_t n = _vertexs.size(); size_t srci =GetVertexIndex(src);// vector<W> dist,记录srci-其他顶点最短路径权值数组 dist.resize(n, MAX_W);// vector<int> pPath 记录srci-其他顶点最短路径父顶点数组 pPath.resize(n,-1);// 先更新srci->srci为缺省值 dist[srci]=W();// 总体最多更新n轮for(size_t k =0; k < n;++k){// i->j 更新松弛bool update =false; cout <<"更新第:"<< k <<"轮"<< endl;for(size_t i =0; i < n;++i){for(size_t j =0; j < n;++j){// srci -> i + i ->jif(_matrix[i][j]!= MAX_W && dist[i]!= MAX_W && dist[i]+ _matrix[i][j]< dist[j]){ update =true;//cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl; dist[j]= dist[i]+ _matrix[i][j]; pPath[j]= i;}}}// 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了if(update ==false){break;}}// 还能更新就是带负权回路for(size_t i =0; i < n;++i){for(size_t j =0; j < n;++j){// srci -> i + i ->jif(_matrix[i][j]!= MAX_W && dist[i]+ _matrix[i][j]< dist[j]){returnfalse;}}}returntrue;}

三、Floyd_Warshall算法

Floyd-Warshall算法是求任意两点之间最短路径的算法,依次把每个点当作中转点,判断从 i 到 j 是直接走更近,还是经过这个中转点 k 再走更近,不断更新所有点对的最短距离,三层循环跑完就得到全图最短路径。

voidFloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath){ size_t n = _vertexs.size(); vvDist.resize(n); vvpPath.resize(n);// 初始化权值和路径矩阵for(size_t i =0; i < n;++i){ vvDist[i].resize(n, MAX_W); vvpPath[i].resize(n,-1);}// 直接相连的边更新一下for(size_t i =0; i < n;++i){for(size_t j =0; j < n;++j){if(_matrix[i][j]!= MAX_W){ vvDist[i][j]= _matrix[i][j]; vvpPath[i][j]= i;}if(i == j){ vvDist[i][j]=W();}}}// 最短路径的更新i-> {其他顶点} ->jfor(size_t k =0; k < n;++k){for(size_t i =0; i < n;++i){for(size_t j =0; j < n;++j){// k 作为的中间点尝试去更新i->j的路径if(vvDist[i][k]!= MAX_W && vvDist[k][j]!= MAX_W && vvDist[i][k]+ vvDist[k][j]< vvDist[i][j]){ vvDist[i][j]= vvDist[i][k]+ vvDist[k][j];// 找跟j相连的上一个邻接顶点// 如果k->j 直接相连,上一个点就k,vvpPath[k][j]存就是k// 如果k->j 没有直接相连,k->...->x->j,vvpPath[k][j]存就是x vvpPath[i][j]= vvpPath[k][j];}}}}}

Read more

【C++】适配器模式手搓STL的stack和queue

【C++】适配器模式手搓STL的stack和queue

适配器模式手搓STL的stack和queue * 适配器模式实现STL的stack和queue * github地址 * 0. 前言 * 1. stack和queue的简单介绍 * 1.1 stack * 1.2 queue * 2. 容器适配器 * 2.1 什么是适配器 * 2.2 C++中的适配器 * 3. 手搓stack和queue * 3.1 实现stack * 基础架构 * push和pop * 数据访问 * 容量访问 * 完整实现 * 3.2 实现queue * 基础架构 * push和pop * 数据访问 * 容量访问 * 完整实现 * 4. deque简单剖析 * 简介 * deque无法替代vector和list * 作为容器适配器 * deque的迭代器 * 5. 结语 适配器模式实现STL的stack和queue github地址

By Ne0inhk
【C++详解】C++ 智能指针:使用场景、实现原理与内存泄漏防治

【C++详解】C++ 智能指针:使用场景、实现原理与内存泄漏防治

文章目录 * 一、智能指针的使⽤场景分析 * 二、RAII和智能指针的设计思路 * 三、C++标准库智能指针的使⽤ * 四、智能指针的原理 * shared_ptr源码 * 五、shared_ptr和weak_ptr * shared_ptr循环引⽤问题 * weak_ptr * 六、内存泄漏 * 什么是内存泄漏,内存泄漏的危害 * 如何避免内存泄漏 一、智能指针的使⽤场景分析 我们知道C++是是公认的高效编程语言,其中一点原因就是C++手动内存管理(new/delete),避免了很多高级语言(如 Java、Python)的自动内存管理(垃圾回收)带来的额外开销,这也是一把双刃剑,这对C++程序员的要求就会更高,因为手动内存管理很容易出现内存泄漏的问题,我们之前的说法是尽可能小心,但是有些场景无法避免会出现内存泄漏(或者处理起来很麻烦)

By Ne0inhk
【C++】 —— 笔试刷题day_18

【C++】 —— 笔试刷题day_18

一、压缩字符串(一) 题目解析 题目给定一个字符str,让我们将这个字符串进行压缩; **压缩规则:**出现多次的字符压缩成字符+数字;例如aaa压缩成a3。如果字符值出现一次,1不用写。 算法思路 这道题总的来说就非常简单了,我们直接模拟整个过程即可。 思路: 示例双指针遍历,统计字符和字符出现的次数; i固定一个字符,j向后遍历找与i位置相同的字符,如果相同就继续向后遍历,直到j位置与i位置的字符不相同; j向后遍历结束,i位置字符出现的字符次数为j-i;如果j-1大于1就在结果字符串中加入出现的次数;等于1则不用加次数。 代码实现 classSolution{public: string compressString(string param){ string ret;for(int i =0;i<param.size();){int j = i+1;while(j<

By Ne0inhk

C++:实现字符串分割split函数(附带源码)

项目背景详细介绍 在实际的软件开发过程中,字符串处理是最基础、也是最常见的需求之一。无论是系统底层开发、网络通信、日志分析,还是 Web 后端、工具类程序,字符串的解析与拆分都无处不在。 在很多高级语言中(如 Python、JavaScript、Java),字符串分割函数是语言内建能力: * Python:str.split() * Java:String.split() * JavaScript:String.split() 然而在 C++ 标准库中,并没有一个直接、统一、易用的 split 函数。这就导致: * 初学者不知道如何优雅地拆分字符串 * 面试和笔试中 split 函数几乎是“必写题” * 工程中经常需要重复实现自己的 split 工具函数 因此,实现一个通用、健壮、可扩展的 split 函数,

By Ne0inhk