赞
踩
目录
图是由顶点集合和边的集合组成的一种数据结构,记作
有向图与无向图
完全图
邻接顶点
顶点的度
路径与路径长度
带权图示例:
简单路径与回路
子图
设图 和图 ,若 且 ,则称 是 的子图
连通图和强连通图
生成树与最小生成树
图的相关应用场景
图常见的表示场景如下:
关于有向图和无向图:
图的其他相关作用:
图与树的联系与区别
图由顶点和边组成,存储图的本质就是将图中的顶点和边存储起来
邻接矩阵存储图的方式
说明:
邻接矩阵的优缺点
优点:
缺点:
邻接矩阵的实现
成员变量:
实现原理:
- #include <iostream>
- #include <vector>
- #include <unordered_map>
- using namespace std;
-
- namespace matrix
- {
- template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
- class Graph
- {
- public:
- Graph() = default;
- Graph(const V* vertex, size_t size)
- :_vertexs(vertex, vertex + size), _matrix(size, vector<int>(size, MAX_W))
- {
- for (int i = 0; i < size; ++i)
- _indexMap[vertex[i]] = i;
- }
-
- //获取顶点对应的下标
- int GetVertexIndex(const V& v)
- {
- auto it = _indexMap.find(v);
- if (it != _indexMap.end()) //顶点存在
- return it->second;
- else { //顶点不存在
- throw invalid_argument("顶点不存在");
- return -1;
- }
- }
-
- //添加边
- void AddEdge(const V& source, const V& destion, const W& weight)
- {
- int sourceIndex = GetVertexIndex(source), destionIndex = GetVertexIndex(destion);
- _matrix[sourceIndex][destionIndex] = weight;
- if (Direction == false)
- _matrix[destionIndex][sourceIndex] = weight;
- }
-
- //打印顶点集合和邻接矩阵
- void Print()
- {
- int size = _vertexs.size();
- //打印顶点集合
- for (int i = 0; i < size; i++)
- cout << "[" << i << "]->" << _vertexs[i] << endl;
- cout << endl;
-
- //打印邻接矩阵
- cout << " ";
- for (int i = 0; i < size; i++)
- printf("%4d", i);
- cout << endl;
-
- for (int i = 0; i < size; i++)
- {
- cout << i << " "; //竖下标
- for (int j = 0; j < size; j++)
- {
- if (_matrix[i][j] == MAX_W) printf("%4c", '*');
- else printf("%4d", _matrix[i][j]);
- }
- cout << endl;
- }
- cout << endl;
- }
-
- private:
- vector<V> _vertexs; //顶点集合
- unordered_map<V, int> _indexMap; //顶点映射下标
- vector<vector<int>> _matrix; //邻接矩阵
- };
- }
邻接表存储图的方式
说明:
邻接表的优缺点
优点:
缺点:
邻接表的实现
链表结点成员变量:
- namespace link_table
- {
- //链表结点
- template<class W>
- struct Edge
- {
- Edge(int dstI, const W& w) : _destionIndex(dstI), _weight(w), _next(nullptr) {}
- //int _sourceIndex; //可选
- int _destionIndex;
- W _weight;
- Edge<W>* _next;
- };
- }
邻接表成员变量:
实现原理:
- namespace link_table
- {
- template<class V, class W, bool Direction = false>
- class Graph
- {
- typedef Edge<W> Edge;
- public:
- Graph() = default;
- Graph(const V* vertexs, size_t size)
- :_vertexs(vertexs, vertexs + size), _linkTable(size, nullptr)
- {
- for (int i = 0; i < size; ++i)
- _indexMap[vertexs[i]] = i;
- }
-
- int GetVertexIndex(const V& vertex)
- {
- auto it = _indexMap.find(vertex);
- if (it != _indexMap.end()) return it->second;
- else {
- throw invalid_argument("不存在的顶点");
- return -1;
- }
- }
-
- void AddEdge(const V& src, const V& dst, const W& w)
- {
- int srcIndex = GetVertexIndex(src), dstIndex = GetVertexIndex(dst);
-
- Edge* newEdge = new Edge(dstIndex, w);
- newEdge->_next = _linkTable[srcIndex];
- _linkTable[srcIndex] = newEdge;
-
- if (Direction == false)
- {
- Edge* newEdge = new Edge(srcIndex, w);
- newEdge->_next = _linkTable[dstIndex];
- _linkTable[dstIndex] = newEdge;
- }
- }
-
- void Print()
- {
- int size = _vertexs.size();
- //打印顶点集合
- for (int i = 0; i < size; i++)
- cout << "[" << i << "]->" << _vertexs[i] << " ";
- cout << endl << endl;
- //打印邻接表
- for (int i = 0; i < size; i++)
- {
- Edge* cur = _linkTable[i];
- cout << "[" << i << ":" << _vertexs[i] << "]->";
- while (cur) {
- cout << "[" << cur->_destionIndex << ":" << _vertexs[cur->_destionIndex] << ":" << cur->_weight << "]->";
- cur = cur->_next;
- }
- cout << "nullptr" << endl;
- }
- }
-
- private:
- vector<V> _vertexs; //顶点集合
- unordered_map<V, int> _indexMap; //映射关系
- vector<Edge*> _linkTable; //出边表
- };
- }
图的遍历指的是遍历图中的顶点,主要有广度优先遍历和深度优先遍历两种方式
广度优先遍历又称BFS,类似于二叉树的层序遍历,从起始顶点开始一层一层向外进行遍历
实现原理:
- void BFS(const V& source)
- {
- int sourceIndex = GetVertexIndex(source);
- queue<int> qe;
- vector<bool> visited(_vertexs.size(), false);
- qe.push(sourceIndex);
- visited[sourceIndex] = true;
-
- while (!qe.empty())
- {
- int front = qe.front();
- qe.pop();
- cout << _vertexs[front] << " ";
- for (int j = 0; j < _vertexs.size(); ++j)
- {
- if (_matrix[front][j] != MAX_W && visited[j] == false)
- {
- qe.push(j);
- visited[j] = true;
- }
- }
- }
- cout << endl;
- }
说明:
深度优先遍历又称DFS,类似于二叉树的先序遍历,从起始顶点开始不断对顶点进行深入遍历
实现原理:
- void _DFS(int srcIndex, vector<bool>& visvited)
- {
- cout << _vertexs[srcIndex] << " "; // 访问
- visvited[srcIndex] = true;
- for (int j = 0; j < _vertexs.size(); ++j)
- if (_matrix[srcIndex][j] != MAX_W && visvited[j] == false)
- _DFS(j, visvited);
- }
- void DFS(const V& source)
- {
- int sourceIndex = GetVertexIndex(source);
- vector<bool> visvited(_vertexs.size(), false);
- _DFS(sourceIndex, visvited);
- cout << endl;
- }
若所给图不是一个连通图,那么从一个顶点开始进行深度优先遍历,无法遍历完图中的所有顶点。这时可以遍历标记数组,查看哪些顶点还没有被访问过,对于没有被访问过的顶点,则从该顶点处继续进行深度优先遍历,直到图中所有的顶点都被访问过
认识最小生成树
说明:
构成最小生成树的准则
构造最小生成树的算法有 Kruskal(克鲁斯卡尔)算法和 Prim(普里姆)算法,这两个算法都采用了逐步求解的贪心策略
基本思想
具体实现
- //添加边
- void _AddEdge(int& srcIndex, int& dstIndex, const W& weight)
- {
- _matrix[srcIndex][dstIndex] = weight;
- if (Direction == false)
- _matrix[dstIndex][srcIndex] = weight;
- }
- void AddEdge(const V& source, const V& destion, const W& weight)
- {
- int sourceIndex = GetVertexIndex(source), destionIndex = GetVertexIndex(destion);
- _AddEdge(sourceIndex, destionIndex, weight);
- }
-
- struct Edge
- {
- Edge(int srcIndex, int dstIndex, const W& weight)
- :_sourceIndex(srcIndex), _destionIndex(dstIndex), _weight(weight) {}
- bool operator>(const Edge& edge)const {
- return _weight > edge._weight;
- }
- int _sourceIndex;
- int _destionIndex;
- W _weight;
- };
- W Kruskal(Graph<V, W, MAX_W, Direction>& minTree)
- {
- int size = _vertexs.size();
- //最小生成树初始化
- minTree._vertexs = _vertexs;
- minTree._indexMap = _indexMap;
- minTree._matrix.resize(size, vector<W> (size, MAX_W));
-
- priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;//小堆
- for (int i = 0; i < size; ++i)
- for (int j = 0; j < i; ++j)//只遍历矩阵一半,避免添加重复的边
- if (_matrix[i][j] != MAX_W)
- minHeap.push(Edge(i, j, _matrix[i][j]));
-
- UnionFindSet ufs(size); //size个顶点的并查集, 用于判环
- int count = 0; //已选边的数量
- W totalWeight = W(); //最小生成树的总权值
- while (!minHeap.empty() && count < size - 1)
- {
- //获取此时最小的边
- Edge minEdge = minHeap.top();
- minHeap.pop();
- int srcI = minEdge._sourceIndex, dstI = minEdge._destionIndex;
- W weight = minEdge._weight;
- if (!ufs.IsInSet(srcI, dstI)) //边的源顶点和目标顶点不在同一个集合, 即无环
- {
- minTree._AddEdge(srcI, dstI, weight);
- ufs.Union(srcI, dstI);
- ++count;
- totalWeight += weight;
- cout << "选边: " << _vertexs[srcI] << "->" << _vertexs[dstI] << ":" << weight << endl;
- }
- }
- if (count == size - 1) {
- cout << "构建最小生成树成功" << endl;
- return totalWeight;
- }
- else {
- cout << "无法构成最小生成树" << endl;
- return W();
- }
- }
基本思想
具体实现
- W Prim(Graph<V, W, MAX_W, Direction>& minTree, const V& start)
- {
- int size = _vertexs.size();
- //最小生成树初始化
- minTree._vertexs = _vertexs;
- minTree._indexMap = _indexMap;
- minTree._matrix.resize(size, vector<W>(size, MAX_W));
-
- int startIndex = GetVertexIndex(start);
- vector<bool> forest(size, false);
- forest[startIndex] = true;
- priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;
-
- //将初始顶点连接的边加入堆中
- for (int j = 0; j < size; ++j)
- if (_matrix[startIndex][j] != MAX_W)
- minHeap.push(Edge(startIndex, j, _matrix[startIndex][j]));
-
- int count = 0;
- W totalWeight = W();
- while (!minHeap.empty() && count < size - 1)
- {
- Edge minEdge = minHeap.top();
- minHeap.pop();
- int srcIndex = minEdge._sourceIndex, dstIndex = minEdge._destionIndex;
- W weight = minEdge._weight;
-
- if (forest[dstIndex] == false) // 边的目标顶点还没有被加入到forest集合中
- {
- //将目标顶点连接出去的边加入到优先级队列中
- for (int j = 0; j < size; ++j)
- if (_matrix[dstIndex][j] != MAX_W)
- minHeap.push(Edge(dstIndex, j, _matrix[dstIndex][j]));
- minTree._AddEdge(srcIndex, dstIndex, weight);
- forest[dstIndex] = true;
- ++count;
- totalWeight += weight;
- cout << "选边: " << _vertexs[srcIndex] << "->" << _vertexs[dstIndex] << ":" << weight << endl;
- }
- }
- if (count == size - 1) {
- cout << "构建最小生成树成功" << endl;
- return totalWeight;
- }
- else {
- cout << "无法构成最小生成树" << endl;
- return W();
- }
- }
注意:使用前提,图中所有边的权值非负
迪杰斯特拉算法基本思想
具体实现
- void Dijkstra(const V& source, vector<W>& dist, vector<int>& parentPath)
- {
- int size = _vertexs.size();
- int srcIndex = GetVertexIndex(source);
- dist.resize(size, MAX_W);
- dist[srcIndex] = W();
- parentPath.resize(size, -1);
-
- vector<bool> S(size, false);//已确定最短路径的顶点的集合
- for (int i = 0; i < size; ++i)//目标是将Q集合中的所有顶点都加入S集合
- {
- //从集合Q中选出一个估计值最小的顶点
- W minW = MAX_W; //记录最小估计值
- int u = -1; //记录拥有最小估计值的顶点
- for(int j = 0; j < size; ++j)
- if (S[j] == false && dist[j] < minW) {
- minW = dist[j];
- u = j;
- }
- //将选出的顶点加入S集合
- S[u] = true;
- //对u连接出去的顶点进行松弛更新
- for (int v = 0; v < size; ++v)
- {
- if (S[v] == false && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v])
- {
- dist[v] = dist[u] + _matrix[u][v];
- parentPath[v] = u;
- }
- }
- }
- }
-
- //打印最短路径及路径权值
- void PrintShortPath(const V& src, const vector<W>& dist, const vector<int>& parentPath)
- {
- int n = _vertexs.size();
- int srci = GetVertexIndex(src); //获取源顶点的下标
- for (int i = 0; i < n; i++)
- {
- vector<int> path;
- int cur = i;
- while (cur != -1) { //源顶点的前驱顶点为-1
- path.push_back(cur);
- cur = parentPath[cur];
- }
- reverse(path.begin(), path.end()); //逆置
- for (int j = 0; j < path.size(); j++) {
- cout << _vertexs[path[j]] << "->";
- }
- cout << "路径权值: " << dist[i] << "" << endl;
- }
- }
算法原理
贝尔曼福特算法基本思想
具体实现
- bool BellmanFord(const V& source, vector<W>& dist, vector<int>& parentPath)
- {
- int size = _vertexs.size();
- int srcIndex = GetVertexIndex(source);
- dist.resize(size, MAX_W);
- dist[srcIndex] = W();
- parentPath.resize(size, -1);
-
- for (int k = 0; k < size - 1; ++k) // 最多更新size - 1轮
- {
- bool update = false;//记录本轮是否更新过
- for (int i = 0; i < size; ++i)
- {
- for (int j = 0; j < size; ++j)
- {
- if (_matrix[i][j] != MAX_W && dist[i] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
- {
- dist[j] = dist[i] + _matrix[i][j];
- parentPath[j] = i;
- update = true;
- }
- }
- }
- if (update == false) break;
- }
- for (int i = 0; i < size; ++i)
- for (int j = 0; j < size; ++j)
- if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
- return false; //带有负权回路的图无法求出最短路径
- return true;
- }
为什么最多进行 n-1 轮松弛更新?
从一个顶点到另一个顶点的最短路径中不能包含回路:
在每一轮松弛过程中,后面路径的更新可能会影响到前面已经更新过的路径,比如使得前面已经更新过的路径的长度可以变得更短,或者使得某些源顶点之前不可达的顶点变得可达,但每一轮松弛至少能确定最短路径中的一条边,若图中有 个顶点,那么两个顶点之间的最短路径最多有 条边,因此最多需要进行 次松弛更新。
下图中,顶点 、、、、 的下标分别是0、1、2、3、4,现在要计算以顶点 为源顶点的单源最短路径
对于上述图来说,Bellman-Ford算法在第一轮松弛的时候只能更新出这条边,在第二轮的时候只能更新出 ,以此类推,最终就会进行4轮松弛更新
弗洛伊德算法基本思想
具体实现
- void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvParentPath)
- {
- int size = _vertexs.size();
- vvDist.resize(size, vector<W>(size, MAX_W));
- vvParentPath.resize(size, vector<int>(size, -1));
-
- //根据邻接矩阵初始化直接相连的顶点
- for (int i = 0; i < size; ++i)
- {
- for (int j = 0; j < size; ++j)
- {
- if (_matrix[i][j] != MAX_W)//i -> j 有边
- {
- vvDist[i][j] = _matrix[i][j];
- vvParentPath[i][j] = i;
- }
- if (i == j) vvDist[i][j] = W();
- }
- }
- for (int k = 0; k < size; ++k)//依次选取各个顶点作为i->j路径的中间顶点
- for (int i = 0; i < size; ++i)
- for (int j = 0; j < size; ++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];
- vvParentPath[i][j] = vvParentPath[k][j];
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。