【数据结构】图算法(代码)
一、图的遍历
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算法 的时候要先了解一下什么是并查集。
并查集

一趟火车之旅后,每个小分队成员就互相熟悉,称为了一个朋友圈。
并查集一般可以解决以下问题:
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();}}
验证一下:
三、最短路径
3.1、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算法
贝尔曼-福特算法
步骤:以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];}} }}}