文章目录

  • 连通图与连通分量
  • 强连通图与强连通分量
  • 图的连通性判断
  • 生成树
    • 深度优先生成树
      • 邻接表
      • 邻接矩阵
    • 广度优先生成树
      • 邻接表
      • 邻接矩阵
    • 生成森林
  • 获取边弧的权值
  • 源代码

连通图与连通分量

在无向图中, 若从顶点v到顶点w有路径存在, 则称v和w是连通的. 若图G中任意两个顶点都是连通的, 则称图G为连通图, 否则称为非连通图. 无向图中的极大连通子图称为连通分量, 在图(a)中, 图G有3个连通分量如图(b)所示.

假设一个图有n个顶点, 如果边数小于n-1, 那么此图必是非连通图. 如果图是非连通图, 那么最多可以有多少条边?

强连通图与强连通分量

在有向图中, 如果有一对顶点v和w, 从v到w和从w到v之间都有路径, 则称这两个顶点是强连通的. 若图中任何一对顶点都是强连通的, 则称此图为强连通图. 有向图中的极大强连通子图称为有向图的强连通分量, 图G的强连通分量如图(b)所示。

假设一个有向图有n个顶点, 如果是强连通图, 那么最少需要有多少条边.

图的连通性判断

图的遍历算法可以用来判断图的连通性.

对于无向图来说, 若无向图是连通的, 则从任一结点出发, 仅需一次遍历就能够访问图中的所有顶点; 若无向图是非连通的, 则从某一个顶点出发, 一次遍历只能访问到该顶点所在连通分量的所有顶点, 而对于图中其他连通分量的顶点, 则无法通过这次遍历访问.

对于有向图来说, 若从某一顶点到图中的每个顶点都有路径, 则能够访问到图中的所有顶点, 否则不能访问到所有顶点. 因此判断有向图的强连通性, 需要依次从所有顶点出发遍历, 只有从所有顶点出发遍历到其它所有顶点, 该有向图才具有强连通性, 如果从某一个顶点出发遍历, 不能连续遍历到其它顶点, 则该有向图不具有强连通性.

邻接表

     bool _IsConnected(int srci){int n = static_cast<int>(_vertexSet.size());vector<bool> markbit(n, false);queue<int> q;q.push(srci);markbit[srci] = true;// 类似广度优先遍历的思想while (!q.empty()){auto front = q.front();q.pop();Edge* curr = _table[front];while (curr != nullptr){if (!markbit[curr->_dsti]){q.push(curr->_dsti);markbit[curr->_dsti] = true;}curr = curr->_next;}}// 如果还有顶点没有遍历到,说明不是连通图for (int i = 0; i < n; i++){if (!markbit[i]){return false;}}return true;}bool IsConnected(){if (_vertexSet.empty())return true;if (Directed) // 有向图的强连通性判断{int n = static_cast<int>(_vertexSet.size());// 需要依次从所有顶点出发遍历for (int i = 0; i < n; i++){if (!_IsConnected(i)){return false;}}return true;}else // 无向图的连通性判断{return _IsConnected(0);}}

邻接矩阵

     bool _IsConnected(int srci){int n = static_cast<int>(_vertexSet.size());vector<bool> markbit(n, false);queue<int> q;q.push(srci);markbit[srci] = true;while (!q.empty()){auto front = q.front();q.pop();for (int i = 0; i < n; i++){if (_matrix[front][i] != W_MAX && !markbit[i]){q.push(i);markbit[i] = true;}}}for (int i = 0; i < n; i++){if (!markbit[i]){return false;}}return true;}bool IsConnected(){if (_vertexSet.empty())return false;if (Directed){int n = static_cast<int>(_vertexSet.size());for (int i = 0; i < n; i++){if (!_IsConnected(i)){return false;}}return true;}else{return _IsConnected(0);}}

生成树

一个连通图的生成树是一个极小连通子图, 它含有图中全部n个顶点, 但只有足以构成一棵树的n-1条边. 在生成树中添加一条边之后, 必然会形成回路/环. 一般来说, 一个连通图的生成树并不是唯一的, 除非原图本身就是一棵树.

深度优先生成树

采用DFS算法遍历图所得到的生成树称为深度优先生成树.

邻接表

邻接矩阵

广度优先生成树

采用BFS算法遍历图所得到的生成树称为广度优先生成树.

邻接表

邻接矩阵

生成森林

若无向图G是非连通图, 从图中某一顶点出发遍历图, 不能访问到该图的所有顶点, 需要依次对图中的每一个连通分量进行深度优先遍历或者广度优先遍历, 即需要从多个顶点出发进行DFS或者BFS.

在遍历过程中, 如果将每次前进途中路过的顶点与边记录下来, 将会得到多棵树, 从而构成森林.

  • 采用DFS算法遍历图所得到的生成森林称为广度优先生成森林.
  • 采用BFS算法遍历图所得到的生成森林称为广度优先生成森林.

获取边弧的权值

邻接表

     // 获取两个相连顶点之间边的权值const W& GetEdgeWeight(const V& src, const V& dst){int srci = GetVertexIndex(src);int dsti = GetVertexIndex(dst);if (srci == -1 || dsti == -1){return W(); // 可以选择抛异常}Edge* curr = _table[srci];while (curr!=nullptr){if (curr->_dsti == dsti){return curr->_weight;}curr=curr->_next;}return W();}

邻接矩阵

     // 获取两个相连顶点之间边的权值const W& GetEdgeWeight(const V& src, const V& dst){int srci = GetVertexIndex(src);int dsti = GetVertexIndex(dst);if (srci == -1 || dsti == -1){return W(); // 可以选择抛异常}return _matrix[srci][dsti];}

源代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
#include <queue>
#include <map>using namespace std;namespace AdjacentMatrix
{template<typename V, typename W, W W_MAX, bool Directed = false>class Graph{private:std::vector<V> _vertexSet;std::map<V, int> _vertexIndex;std::vector<std::vector<W>> _matrix;public:typedef Graph<V, W, W_MAX, Directed> Self;Graph() = default;int GetVertexIndex(const V& v){typename std::map<V, int>::iterator pos = _vertexIndex.find(v);if (pos != _vertexIndex.end()){return pos->second;}else{return -1;}}// 获取两个相连顶点之间边的权值const W& GetEdgeWeight(const V& src, const V& dst){int srci = GetVertexIndex(src);int dsti = GetVertexIndex(dst);if (srci == -1 || dsti == -1){return W(); // 可以选择抛异常}return _matrix[srci][dsti];}bool AddVertex(const V& v){// 顶点存在不需要继续增加if (GetVertexIndex(v) != -1)return false;_vertexSet.push_back(v);_vertexIndex.insert(std::make_pair(v, _vertexSet.size() - 1));// 先在原有的行上一列for (int i = 0; i < _matrix.size(); i++){_matrix[i].push_back(W_MAX);}// 增加一行_matrix.push_back(std::vector<W>(_vertexSet.size(), W_MAX));return true;}bool AddEdge(const V& src, const V& dst, const W& weight){int srci = GetVertexIndex(src);int dsti = GetVertexIndex(dst);// 顶点不在图中,添加边失败if (srci == -1 || dsti == -1)return false;_matrix[srci][dsti] = weight;// 如果为无向图,则需要再添加一条dst->src的边if (!Directed){_matrix[dsti][srci] = weight;}return true;}bool bfsSpanningTree(Self& spanningTree, const V& src){int srci = GetVertexIndex(src);// 遍历源点不存在,不能进行遍历if (srci == -1){return false;}// 只有图为无向图且是连通图才能选出生成树if (Directed && !IsConnected())return false;// 将spanningTree拷贝成与当前图相同顶点的零图spanningTree._vertexSet = _vertexSet;spanningTree._vertexIndex = _vertexIndex;spanningTree._matrix.resize(_vertexSet.size(), vector<W>(_vertexSet.size(), W_MAX));vector<bool> visited(_vertexSet.size(), false);// pair.first是pair.second的上一层顶点下标queue<pair<int,int>> q;// 将遍历源点下标入队,并标记访问过q.push({-1,srci});visited[srci] = true;while (!q.empty()){pair<int,int> front = q.front();q.pop();int first = front.first; // 当前遍历顶点的上一层顶点int second = front.second; // 当前遍历顶点if (first != -1){// 将选出的边添加到spanningTree中std::cout << _vertexSet[first] << "<--->" << _vertexSet[second] << std::endl;spanningTree.AddEdge(_vertexSet[first], _vertexSet[second], _matrix[first][second]);}// 将与front.second相连的顶点且没有被访问的顶点入队for (int i = 0; i < static_cast<int>(_vertexSet.size()); i++){if (_matrix[second][i] != W_MAX && !visited[i]){// 入队之后标记访问过q.push({second,i});visited[i] = true; }}}return spanningTree.IsConnected();}void _dfsSpanningTree(Self& spanningTree, int srci, int& previ, vector<bool>& visited){if (previ != -1){// 将选出的边添加到spanningTree中std::cout << _vertexSet[previ] << "<--->" << _vertexSet[srci] << std::endl;spanningTree.AddEdge(_vertexSet[previ], _vertexSet[srci],_matrix[previ][srci]);}// 标记访问过visited[srci] = true;// 更新前一个顶点的下标previ = srci; // 与DFS遍历的思想相同for (int i = 0; i < static_cast<int>(_vertexSet.size()); i++){if (_matrix[srci][i] != W_MAX && !visited[i]){_dfsSpanningTree(spanningTree, i, previ, visited);}}}bool dfsSpanningTree(Self& spanningTree, const V& src){int srci = GetVertexIndex(src);// 遍历源点不存在,不能进行遍历if (srci == -1){return false;}// 只有图为无向图且是连通图才能选出生成树if (Directed && !IsConnected())return false;// 将spanningTree拷贝成与当前图相同顶点的零图spanningTree._vertexSet = _vertexSet;spanningTree._vertexIndex = _vertexIndex;spanningTree._matrix.resize(_vertexSet.size(), vector<W>(_vertexSet.size(), W_MAX));vector<bool> visited(_vertexSet.size(), false);int previ = -1; // 遍历访问的前一个顶点下标_dfsSpanningTree(spanningTree, srci, previ, visited);return spanningTree.IsConnected(); // 如果spanningTree是连通图则说明构建生成树成功}bool _IsConnected(int srci){int n = static_cast<int>(_vertexSet.size());vector<bool> markbit(n, false);queue<int> q;q.push(srci);markbit[srci] = true;while (!q.empty()){auto front = q.front();q.pop();for (int i = 0; i < n; i++){if (_matrix[front][i] != W_MAX && !markbit[i]){q.push(i);markbit[i] = true;}}}for (int i = 0; i < n; i++){if (!markbit[i]){return false;}}return true;}bool IsConnected(){if (_vertexSet.empty())return false;if (Directed){int n = static_cast<int>(_vertexSet.size());for (int i = 0; i < n; i++){if (!_IsConnected(i)){return false;}}return true;}else{return _IsConnected(0);}}};void TestMatrix3(){AdjacentMatrix::Graph<std::string, int, INT_MAX> g;g.AddVertex("A");g.AddVertex("B");g.AddVertex("C");g.AddVertex("D");g.AddVertex("E");g.AddVertex("F");g.AddVertex("G");g.AddEdge("A", "B", 1);g.AddEdge("A", "C", 1);g.AddEdge("B", "D", 1);g.AddEdge("B", "E", 1);g.AddEdge("C", "E", 1);g.AddEdge("C", "F", 1);g.AddEdge("D", "G", 1);g.AddEdge("E", "G", 1);g.AddEdge("F", "G", 1);AdjacentMatrix::Graph<std::string, int, INT_MAX> spanningTree;//g.dfsSpanningTree(spanningTree, "A");g.bfsSpanningTree(spanningTree, "A");std::cout << spanningTree.IsConnected() << std::endl;}}namespace AdjacentList
{template<typename W>struct Edge{int _dsti;W _weight;struct Edge<W>* _next;Edge(int dsti, const W& weight):_dsti(dsti), _weight(weight), _next(nullptr){}};template<typename V, typename W, bool Directed = false>class Graph{using Edge = Edge<W>;private:std::vector<V> _vertexSet; // 顶点的集合std::map<V, int> _vertexIndex; // 顶点映射下标std::vector<Edge*> _table; // 出度边表public:typedef Graph<V, W,Directed> Self;Graph() = default;int GetVertexIndex(const V& v){typename std::map<V, int>::iterator pos = _vertexIndex.find(v);if (pos != _vertexIndex.end()){return pos->second;}else{return -1;}}bool AddVertex(const V& v){if (GetVertexIndex(v) != -1)return false;_vertexSet.push_back(v);_vertexIndex.insert(std::make_pair(v, _vertexSet.size() - 1));_table.push_back(nullptr);return true;}bool AddEdge(const V& src, const V& dst, const W& weight){int srci = GetVertexIndex(src);int dsti = GetVertexIndex(dst);// 顶点不在图中,添加边失败if (srci == -1 || dsti == -1)return false;Edge* edge = new Edge(dsti, weight);// 头插edge->_next = _table[srci];_table[srci] = edge;// 无向图if (!Directed){edge = new Edge(srci, weight);edge->_next = _table[dsti];_table[dsti] = edge;}return true;}bool _IsConnected(int srci){int n = static_cast<int>(_vertexSet.size());vector<bool> markbit(n, false);queue<int> q;q.push(srci);markbit[srci] = true;// 类似广度优先遍历的思想while (!q.empty()){auto front = q.front();q.pop();Edge* curr = _table[front];while (curr != nullptr){if (!markbit[curr->_dsti]){q.push(curr->_dsti);markbit[curr->_dsti] = true;}curr = curr->_next;}}// 如果还有顶点没有遍历到,说明不是连通图for (int i = 0; i < n; i++){if (!markbit[i]){return false;}}return true;}bool IsConnected(){if (_vertexSet.empty())return true;if (Directed) // 有向图的强连通性判断{int n = static_cast<int>(_vertexSet.size());// 需要依次从所有顶点出发遍历for (int i = 0; i < n; i++){if (!_IsConnected(i)){return false;}}return true;}else // 无向图的连通性判断{return _IsConnected(0);}}// 获取两个相连顶点之间边的权值const W& GetEdgeWeight(const V& src, const V& dst){int srci = GetVertexIndex(src);int dsti = GetVertexIndex(dst);if (srci == -1 || dsti == -1){return W(); // 可以选择抛异常}Edge* curr = _table[srci];while (curr!=nullptr){if (curr->_dsti == dsti){return curr->_weight;}curr=curr->_next;}return W();}bool bfsSpanningTree(Self& spanningTree, const V& src){int srci = GetVertexIndex(src);// 遍历源点不存在,不能进行遍历if (srci == -1){return false;}// 只有图为无向图且是连通图才能选出生成树if (Directed && !IsConnected())return false;// 将spanningTree拷贝成与当前图相同顶点的零图spanningTree._vertexSet = _vertexSet;spanningTree._vertexIndex = _vertexIndex;spanningTree._table.resize(_vertexSet.size(), nullptr);vector<bool> visited(_vertexSet.size(), false);// pair.first是pair.second的上一层顶点下标queue<pair<int, int>> q;// 将遍历源点下标入队,并标记访问过q.push({ -1,srci });visited[srci] = true;while (!q.empty()){pair<int, int> front = q.front();q.pop();int first = front.first; // 当前遍历顶点的上一层顶点int second = front.second; // 当前遍历顶点if (first != -1){// 将选出的边添加到spanningTree中std::cout << _vertexSet[first] << "<--->" << _vertexSet[second] << std::endl;spanningTree.AddEdge(_vertexSet[first], _vertexSet[second], GetEdgeWeight(_vertexSet[first], _vertexSet[second]));}Edge* curr = _table[second];// 将与front.second相连的顶点且没有被访问的顶点入队while (curr != nullptr){if (!visited[curr->_dsti]){q.push({ second,curr->_dsti });visited[curr->_dsti] = true;}curr = curr->_next;}}return spanningTree.IsConnected();}void _dfsSpanningTree(Self& spanningTree, int srci, int& previ, vector<bool>& visited){if (previ != -1){// 将选出的边添加到spanningTree中std::cout << _vertexSet[previ] << "<--->" << _vertexSet[srci] << std::endl;spanningTree.AddEdge(_vertexSet[previ], _vertexSet[srci], GetEdgeWeight(_vertexSet[previ], _vertexSet[srci]));}// 标记访问过visited[srci] = true;// 更新前一个顶点的下标previ = srci;Edge* curr = _table[srci];while (curr != nullptr){if (!visited[curr->_dsti]){_dfsSpanningTree(spanningTree, curr->_dsti, previ, visited);}curr = curr->_next;}}// spanningTree为输出型参数,src为遍历源点bool dfsSpanningTree(Self& spanningTree, const V& src){int srci = GetVertexIndex(src);// 遍历源点不存在,不能进行遍历if (srci == -1){return false;}// 只有图为无向图且是连通图才能选出生成树if (Directed && !IsConnected())return false;// 将spanningTree拷贝成与当前图相同顶点的零图spanningTree._vertexSet = _vertexSet;spanningTree._vertexIndex = _vertexIndex;spanningTree._table.resize(_vertexSet.size(), nullptr);vector<bool> visited(_vertexSet.size(), false);int previ = -1; // 遍历访问的前一个顶点下标_dfsSpanningTree(spanningTree, srci, previ, visited);return spanningTree.IsConnected(); // 如果spanningTree是连通图则说明构建生成树成功}};void TestList3(){ AdjacentList::Graph<std::string, int> g;g.AddVertex("A");g.AddVertex("B");g.AddVertex("C");g.AddVertex("D");g.AddVertex("E");g.AddVertex("F");g.AddVertex("G");g.AddEdge("A", "B", 1);g.AddEdge("A", "C", 1);g.AddEdge("B", "D", 1);g.AddEdge("B", "E", 1);g.AddEdge("C", "E", 1);g.AddEdge("C", "F", 1);g.AddEdge("D", "G", 1);g.AddEdge("E", "G", 1);g.AddEdge("F", "G", 1);AdjacentList::Graph<std::string, int> spanningTree;//g.dfsSpanningTree(spanningTree, "A");g.bfsSpanningTree(spanningTree, "A");std::cout << spanningTree.IsConnected() << std::endl;}
}int main(int, char**, char**)
{AdjacentList::TestList3();//AdjacentMatrix::TestMatrix3();return 0;
}

图的生成树与生成森林相关推荐

  1. 这次一定弄懂完全图、连通图、连通分量、强连通图、强连通分量、极大连通分量、极小联通分量、生成树、生成森林的区别

    一.各个概念的定义 1.完全图:  也称简单完全图.假设一个图有n个顶点,那么如果任意两个顶点之间都有边的话,该图就称为完全图. 2.连通图(一般都是指无向图):  从顶点v到w有路径,就称顶点v和m ...

  2. 图之深度优先生成森林

    生成森林是指由非连通图的连通分量的生成树所组成的森林,而深度优先生成森林则指由图的深度优先遍历算法获得的生成森林. 下面给出非连通图的深度优先生成森林算法的Java实现,其中生成森林采用孩子兄弟链表存 ...

  3. 最小生成树(kruskal、prim、最小生成森林问题、严格次小生成树)

    整理的算法模板合集: ACM模板 目录 一.kruskal算法 二.prim算法 三.Boruvka算法 四.生成森林问题(K颗树) 五.最小生成树的唯一性 六.严格次小生成树 LCA优化的次小生成树 ...

  4. 7000 字 23 张图,Pandas一键生成炫酷的动态交互式图表

    作者 | 俊欣 来源 | 关于数据分析与可视化 今天小编来演示一下如何用pandas一行代码来绘制可以动态交互的图表,并且将绘制的图表组合到一起,组成可视化大屏,本次小编将要绘制的图表有 折线图 散点 ...

  5. R语言生成组合图并保存实战:实际上只保存了最后一个图问题、ggsave生成组合图并保存(保存完整组合图)

    R语言生成组合图并保存实战:实际上只保存了最后一个图问题.ggsave生成组合图并保存(保存完整组合图) 目录

  6. 7000字 23张图,Pandas一键生成炫酷的动态交互式图表

    今天小编来演示一下如何用pandas一行代码来绘制可以动态交互的图表,并且将绘制的图表组合到一起,组成可视化大屏,本次小编将要绘制的图表有 折线图 散点图 直方图 柱状图 饼图 面积图 地图 组合图 ...

  7. php生成饼状图 柱形图,求一个饼状图或柱状图php生成类或例子

    求一个饼状图或柱状图php生成类或例子 时间:2006/7/19 6:10:04 作者:佚名 人气:268 PHP代码:---------------------------------------- ...

  8. prim算法_图的生成树之最小生成树(Prim)

    树一般来说是基于图而生的,而树是一种特殊的图:无环连通图. 定义1:对于无向图G和一棵树T来说,若T中与G中所包含的节点相同,但是边不相同,且T恰好是无环的连通图,那么称T为G的生成树. 对于图的生成 ...

  9. 7000 字 23 张图,Pandas 一键生成炫酷的动态交互式图表

    这是「进击的Coder」的第 518 篇技术分享 作者:俊欣 来源:关于数据分析与可视化 " 阅读本文大概需要 13 分钟. " 今天小编来演示一下如何用pandas一行代码来绘制 ...

最新文章

  1. 55 前端构建工具Gulp
  2. 使用OpenCV的findContours获取轮廓并切割(python)
  3. 互联网协议 — TLS — CA 认证
  4. 语音识别学习日志 2019-7-14 语音识别基础知识准备3 {Kmean算法分析与HMM(Hidden Markov Model)模型}
  5. 有了这份程序员面试指南,你离大厂Offer还远吗?| 附推荐书籍
  6. pythonic code_Pythonic Code (Part III)
  7. JVM学习手册(X):查看堆内存使用情况以及排错
  8. OpenCV-美食—鲜美滤镜
  9. Pascal VOC2012
  10. 摩斯电码php源码,PHP实现基于文本的莫斯电码生成器
  11. 我的2023届秋招之旅
  12. 信息与计算科学跨考计算机,信息与计算科学考研的方向介绍
  13. vue中使用element-ui时单元格内换行的问题
  14. 分享卖货小程序制作方法_怎么在微信上做小程序卖货
  15. 详解熵、最大熵、联合熵和条件熵、相对熵以及互信息之间的关系
  16. 解决Kafka消费端错误:o.s.kafka.listener.LoggingErrorHandler : Error while processing: null
  17. 【算法】动画图解Dijkstra算法及其实现代码
  18. 需求调研报告模板_2020年预焙阳极行业市场深度调研及投资前景预测分析报告-电解铝需求仍保持增长态势...
  19. 城市交通指挥与应急疏导广播系统解决方案
  20. Metasploit密码爆破模块

热门文章

  1. 国泰君安证券 神策数据首发《证券行业数字化财富管理 3A3R 指标体系白皮书》...
  2. igl或者libigl库的使用
  3. 自媒体数据运营saas_向媒体宣传您的SaaS
  4. 如何为您的Android手机创建自定义铃声
  5. android部分代码片段(例:判断设备为手机,获取mac地址,软键盘,唤醒屏幕等)
  6. tensorflow-gpu版本使用问题和方法汇总
  7. jsp使用session出现The server encountered an unexpected condition that prevented it from fulfilling the r
  8. ARM模拟器-skyeye(天目)的安装和使用!
  9. JavaWeb项目实战 - SpringBoot日记本系统(第一期)
  10. 膨胀卷积 / 空洞卷积(Dilated convolution)