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

【数据结构】图算法(代码)

一、图的遍历

1.1 图的广度优先遍历

这里我用的上一章节中图的邻接矩阵的结构,进行遍历的。

这里其实跟二叉树中,层序遍历思想差不多,都是前一个节点带后面的节点(A带B,C,D之后依次类推)当然这里有一个难点就是当我们遍历到B的时候我们插入队列的时候有AEC,但是我们只想要E,因为A已经pop掉了,C在队列中。我们该如何操作?用个数组标记一下全部元素,当前要插入队列中的元素是否用过,也就是说当我们元素插入到队列的时候标记一下。

代码理解一下

		void BFS(const V& src){size_t srci = GetVertexIndex(src);queue<size_t> q;// 标记数组vector<bool> visited(_vertexs.size(), false);q.push(srci);visited[srci] = true;size_t levelSize = 1;while (!q.empty()){while (levelSize--){size_t front = q.front();q.pop();cout << front << ":" << _vertexs[front];// 把front顶点的邻接顶点入队列for (size_t i = 0; i < _vertexs.size(); ++i){// 过滤掉已经入队的if (_matrix[front][i] != MAX_W && visited[i] == false){q.push(i);visited[i] = true;}}}cout << endl;// 更新levelSizelevelSize = q.size();}}

测试结果 

1.2 图的深度优先遍历

 这种就是递归,不用管上图标注的对不对,get到思想即可。

		void _DFS(size_t srci, vector<bool>& visited){// 遍历当前结点并标记cout << srci << ":" << _vertexs[srci] << endl;visited[srci] = true;// 寻找当前节点没有被访问过的邻接顶点,继续递归往深度遍历for (size_t i = 0; i < _vertexs.size(); i++){if (_matrix[srci][i] != MAX_W && visited[i] == false){_DFS(i, visited);}}}void DFS(const V& src){size_t srci = GetVertexIndex(src);// 标记数组vector<bool> visited(_vertexs.size(), false);_DFS(srci, visited);}

二、最小生成树

构造最小生成树的方法: Kruskal 算法 Prim 算法 。这两个算法都采用了 逐步求解的贪心策略

在学习 Kruskal算法Prim算法 的时候要先了解一下什么是并查集。

并查集

在一些应用问题中,需要 n 个不同的元素划分成一些不相交的集合 开始时,每个元素自成一个
单元素集合,然后按一定的规律将归于同一组元素的集合合并 。在此过程中 要反复用到查询某一
个元素归属于那个集合的运算 。适合于描述这类问题的抽象数据类型称为 并查集 (union-find
set)
比如:某公司今年校招全国总共招生 10 人,西安招 4 人,成都招 3 人,武汉招 3 人, 10 个人来自不
同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号: {0, 1, 2, 3,
4, 5, 6, 7, 8, 9}; 给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个数。( 负号下文解释 )
毕业后,学生们要去公司上班,每个地方的学生自发组织成小分队一起上路,于是:
西安学生小分队 s1={0,6,7,8} ,成都学生小分队 s2={1,4,9} ,武汉学生小分队 s3={2,3,5} 就相互认识
了, 10 个人形成了三个小团体。假设右三个群主 0,1,2 担任队长,负责大家的出行。

 

 一趟火车之旅后,每个小分队成员就互相熟悉,称为了一个朋友圈。

从上图可以看出:编号 6,7,8 同学属于 0 号小分队,该小分队中有 4 ( 包含队长 0) ;编号为 4 9 的同
学属于 1 号小分队,该小分队有 3 ( 包含队长 1) ,编号为 3 5 的同学属于 2 号小分队,该小分队有 3
个人 ( 包含队长 1)
仔细观察数组中内融化,可以得出以下结论: 
1. 数组的下标对应集合中元素的编号
2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数
3. 数组中如果为非负数,代表该元素双亲在数组中的下标

并查集一般可以解决以下问题: 

1. 查找元素属于哪个集合
        沿着数组表示树形关系往上一直找到根( 即:树中中元素为负数的位置 )
2. 查看两个元素是否属于同一个集合
        沿着数组表示的树形关系往上一直找到树的根,如果根相同表明在同一个集合,否则不在
3. 将两个集合归并成一个集合      
        将两个集合中的元素合并
        将一个集合名称改成另一个集合的名称
4. 集合的个数         
         遍历数组,数组中元素为负数的个数即为集合的个数。

代码实现一下 

class UnionFindSet
{
public:UnionFindSet(int n):_ufs(n, -1){}int FindRoot(int x){int root = x;while (_ufs[root] >= 0)root = _ufs[root];//路径压缩while (_ufs[x] >= 0){int parent = _ufs[x];_ufs[x] = root;x = parent;}return root;}bool Union(int x, int y){int root1 = FindRoot(x);int root2 = FindRoot(y);if (root1 == root2){return false;}// -20    -100 if (_ufs[root1] > _ufs[root2]){swap(root1, root2);}_ufs[root1] += _ufs[root2];_ufs[root2] = root1;return true;}bool InSet(int x1, int x2){return FindRoot(x1) == FindRoot(x2);}int SetSize(){int count = 0;for (auto e : _ufs){if (e < 0)++count;}return count;}
private:vector<int> _ufs;
};

 2.1、Kruskal算法

克鲁斯卡尔算法

步骤:找最小的边 ,不能成环

定义一个边的类 

		struct Edge{size_t _srci;size_t _dsti;W _w;Edge(size_t srci, size_t dsti, const W& w):_srci(srci),_dsti(dsti),_w(w){}bool operator > (const Edge& e) const{return _w > e._w;}};

这里就是先找最小边,并且节点相连的边不为环 ,如何判断相连的边不为环用并查集,比如我们看图f和图g,为什么最后选了7没选6,因为选6这个边构成环了,怎么判断构成的环?这个边的i节点与g节点他们能找到共同的根节点。

代码实现一下 

		W Kruskal(Self& minTree){size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}priority_queue<Edge, vector<Edge>, greater<Edge>> minque;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (i < j && _matrix[i][j] != MAX_W){minque.push(Edge(i, j, _matrix[i][j]));}}}// 依次从priority_queue中选最小n-1条边连接minTree即可(判断构成环的不能选)int size = 0;UnionFindSet ufs(n);W totalW = W();while (!minque.empty()){Edge minEg = minque.top();minque.pop();if (!ufs.InSet(minEg._srci, minEg._dsti)){cout << _vertexs[minEg._srci] << "->" << _vertexs[minEg._dsti] << ":" << minEg._w << endl;minTree._AddEdge(minEg._srci, minEg._dsti, minEg._w);ufs.Union(minEg._srci, minEg._dsti);++size;totalW += minEg._w;}else{cout << " 构成环 : ";cout <<  _vertexs[minEg._srci] << "->" << _vertexs[minEg._dsti] << ":" << minEg._w << endl;}}if (size == n - 1){return totalW;}else{return W();}}

 验证一下

 2.2、Prim算法

普利姆算法 

步骤:通过选中的节点,找未选中的节点的边,且不能成环

 这里我借助了,两个数组X,Y标记该节点是否插入。X表示选中的边,Y表示未选中的边

刚开始我们选了a为节点,这里我们用堆,把a的两个边插入到堆中,并在X中标注a已经选中,在Y中标注为已经选过了。我们这里通过堆排序筛选出了ab这个边最小又因为b不在X中,这里我们在把与b相连的边添加到堆中,这里我们要注意一下重复边ba是不能插入的,因为这个边我们已经选过了,如何判断ba是不能插入的呢?这里我们用到了Y数组我们只要与b相连且未被选中的节点我们就可以插入到堆中。按照这个思路以此类推

代码实现一下:

		W Prim(Self& minTree, const W& src){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}vector<bool> X(n, false);vector<bool> Y(n, true);X[srci] = true;Y[srci] = false;priority_queue<Edge, vector<Edge>, greater<Edge>> minq;for (size_t i = 0; i < n; ++i){if (_matrix[srci][i] != MAX_W){minq.push(Edge(srci, i, _matrix[srci][i]));}}// 选最小size_t size = 0;W totalW = W();while (!minq.empty()){Edge minEg = minq.top();minq.pop();// 最小边的目标点也在X集合,则构成环if (X[minEg._dsti]){// 构成环cout << "构成环: " << minEg._srci << "->" << minEg._dsti << endl;}else{cout << "加边: " << minEg._srci << "->" << minEg._dsti << " [" <<minEg._w<<"]" << endl;minTree._AddEdge(minEg._srci, minEg._dsti, minEg._w);X[minEg._dsti] = true;Y[minEg._srci] = false;++size;totalW += minEg._w;if (size == n - 1)break;for (size_t i = 0; i < n; ++i){if (_matrix[minEg._dsti][i] != MAX_W && Y[i]){minq.push(Edge(minEg._dsti, i, _matrix[minEg._dsti][i]));}}}}// 判断是否只有一个树if (size == n - 1){return totalW;}else{return W();}}

验证一下:

三、最短路径

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

3.1、Dijkstra算法

迪杰斯特拉算法 

Dijkstra 算法存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路
径的最短路径。

步骤:以s为起点,更新相连的节点,选出相连最小的边y,然后更新一下y到其他节点的边,更新相连的节点为最小值。以此类推.... 

这里我定义了一个s,dist,pPath数组。

s数组表示没有选中过的节点,dist表示起始节点到该下标节点的最短路径,pPath数组这里存储他们的父节点。

这里的算法思想:我要找到s数组中没有选中的节点且dist最小,之后进行松弛更新,更新一下该节点到邻接点的距离,之后把选中的节点标注为true表示选过了,之后再在未选中的节点中找dist中最小的值。

// 顶点个数是N  -> 时间复杂度:O(N^2)空间复杂度:O(N)
void Dijkstra(const V& src, vector<int>& pPath, vector<W>& dist)
{	// 初始化一下记录路径和权值(距离)的数组size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();pPath.resize(n, -1);dist.resize(n, MAX_W);// 集合S为已确定最短路径的顶点集合vector<bool> s(n, false);pPath[srci] = srci;dist[srci] = 0;for (size_t i = 0; i < n; i++){// 选最短路径顶点且不在S更新其他路径int u = 0; // u为找到最小路径的下标W minDist = MAX_W;for (size_t j = 0; j < n; j++){if (s[i] == false && dist[i] < minDist){u = i;minDist = dist[i];}}s[u] = true;// 松弛更新,更新起点到它相邻点的距离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;}}}
}

验证一下: 

 

3.2、Bellman-Ford算法

贝尔曼-福特算法

bellman—ford 算法可以解决负权图的单源最短路径问题。
先解释一下为什么迪杰斯特拉算法 解决不了负权图的单源最短路径问题,正因为没有负权值导致所有的节点只走一遍,如果遇到负权值把原来,原点到节点的距离进行修改而这个节点并且已经被标注为true了表示选中过的节点。那么与这个节点相连的节点的值全部都要进行修改,因为原点到这个节点的距离进行了修改。

步骤:以s为起始节点,对与s相连的节点进行松弛,把t,y节点进行更新,之后在依次更新与t,y相连的节点....以此类推....

这里我们还是采用了三个数组s,dist,pPath

这里我们采用暴力搜索的算法,先初始化节点s,然后更新与之相连的边t,y,这次我们不像迪杰斯特拉算法找最小边,而是更新所有t,y的边,然后找到t,y相连的所有节点,再依次更新与之相连的所有边。

		// 解决负权值bool BellmanFord(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();cout << "映射关系: " << endl;for (size_t i = 0; i < _vertexs.size(); ++i){cout << "[" << i << "]" << "->" << _vertexs[i] << endl;}cout << endl;// 总体最多更新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){if (_matrix[i][j] != MAX_W && dist[i] != MAX_W && dist[i] + _matrix[i][j] < dist[j]){update = true;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]){return false;}}}cout << "dist:" << endl;for (size_t i = 0; i < n; i++){cout << "[" << 0 << "]->[" << i << "]->" << dist[i] << endl;}return true;}

效果展示一下:

3.3、Floyd-Warshall算法 

弗洛伊德算法是一种解决多源最短路径问题(任意两点间的最短路径)的算法。

Di,j,k表示从i到j的最短路径,该路径经过的中间结点是剩余的结点组成的集合中的结点,假设经过k个结点,编号为1…k,然后这里就分为了两种情况:

1.如果路径经过了结点k,那么ij的距离就等于ik的距离加上kj的距离,然后剩余就经过k-1个点

2.如果不经过结点k,那ij的距离就等于i到j经过k-1个点(不包括k)的距离

 

		void FloydWarshall(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]存就是xvvpPath[i][j] = vvpPath[k][j];}}	}}}

 

相关文章:

  • 微信小程序中的计算属性库-miniprogram-computed
  • 全新AI驱动Workspace Security 套件发布!Fortinet 电子邮件安全产品矩阵升级
  • docker compose v2版本创建和运行容器
  • 第九章 窗口看门狗(WWDG)
  • 在 macOS 上搭建 Flutter 开发环境
  • 论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(四)
  • 企业产品网络安全日志6月10日-WAF资费消耗排查
  • 【FFmpeg学习(2)】视频概念
  • 【FFmpeg学习(1)】图像表示
  • 如何将数据从 iPhone 传输到笔记本电脑
  • HTML盒子模型
  • 《网络世界的“隐形窥探者”:深度剖析网络监听》
  • SCAU期末笔记 - 数据分析与数据挖掘题库解析
  • 基于GeoTools求解GeoTIFF的最大最小值方法
  • AI时代,数据分析师如何成为不可替代的个体
  • 访问服务器项目,服务器可以ping通,但是端口访问不到
  • 通义灵码 AI IDE 上线!智能体+MCP 从手动调用工具过渡到“AI 主动调度资源”
  • 基于服务器使用 apt 安装、配置 Nginx
  • 如何保障服务器的安全
  • synchronized 学习
  • 阿里巴巴怎么建设网站/seo点击排名软件哪家好
  • 做app的模板下载网站有哪些/如何优化企业网站
  • 网站内容建设招标/成都百度推广电话号码是多少
  • vs2013做网站教程/中国最新消息新闻
  • 网站站建设建设中页中页/优化设计三年级上册语文答案
  • 桐城网站定制/百度推广方式有哪些