该系列文章是本人整理的有关带权无向图的数据结构和算法的分析与实现,若要查看源码可以访问我的github仓库,如有问题或者建议欢迎各位指出。
目录
基于C++的带权无向图的实现 (一)- 数据结构
基于C++的带权无向图的实现 (二)- 遍历算法
基于C++的带权无向图的实现 (三)- Prim最小生成树算法
基于C++的带权无向图的实现 (四)- Dijkstra最短路径算法
基于C++的带权无向图的实现 (五)- 连通图和连通分量
基于C++的带权无向图的实现 (六)- 关节点算法
连通图
在图G = {V, {E} } 中,若对于任何两个顶点u, v 都存在从v到u的路径,则称G是连通图,如下所示:
如何测试一个图是否连通呢,很简单,只需要使用图的遍历(深度优先或广度优先)即可,如果从一个顶点出发可以访问到图中所有顶点,那么该图就是连通图。
连通分量
百度百科对连通分量的描述为:
无向图 G的一个极大连通子图称为 G的一个连通分量(或连通分支)。连通图只有一个连通分量,即其自身;非连通的无向图有多个连通分量。
极大连通子图的意思为:该子图是G的连通子图。如果将图G的任何不存在与该子图中的顶点放入,该子图将不再连通,也就是说就是新加入的这个顶点没有到达该子图中任何一个顶点的路径。
例如,对于下面的非连通图,存在极大连通子图g1和g2,他们都是图G的连通分量。
假如我移除顶点2与顶点5的边后再将其放到子图g1中,那么子图g1就不是图G的极大连通子图了,因为子图g1中的顶点1,3,4都没有能到达顶点2的路径,如下所示:
那么找到图中的连通分量的算法该如何实现呢?其实上文测试该图是否为连通图一样,只需要用到图的遍历算法即可:
- 当所有的顶点尚未全部访问时:
- 选择一个未访问的顶点使用遍历算法(深度优先或者广度优先)
- 将这个顶点能到达的所有顶点标记为已访问,并且把他们放到一个连通分量的数组中。
本文将通过深度优先遍历来求图中的连通分量。
代码实现
在Graph类中除了上节内容实现的功能外,额外添加了求连通图的算法,T为提前定义好的模板:
函数名 | 用途 |
---|---|
vector<vector> get_connected_components() | 求连通分量 |
void print_connected_components( const vector<vector>& connected_components ) | 打印连通分量 |
- 边的定义(edge.hpp):
template <typename T>
class Edge {
public:
T vertex;
int weight;
Edge(T neighbour_vertex) {
this->vertex = neighbour_vertex;
this->weight = 0;
}
Edge(T neighbour_vertex, int weight) {
this->vertex = neighbour_vertex;
this->weight = weight;
}
bool operator<(const Edge& obj) const {
return obj.vertex > vertex;
}
bool operator==(const Edge& obj) const {
return obj.vertex == vertex;
}
};
- 图的定义(graph.hpp)
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<limits.h>
#include "edge.hpp"
using namespace std;
template <typename T>
class Graph {
public:
map<T, set<Edge<T>>> adj; /* 邻接表 */
bool contains(const T& u); /* 判断顶点u是否在图中 */
bool adjacent(const T& u, const T& v); /* 判断顶点u和v是否相邻 */
void add_vertex(const T& u); /* 添加顶点 */
void add_edge(const T& u, const T& v, int weight); /* 添加边和权重 */
void change_weight(const T& u, const T& v, int weight); /* 修改权重 */
void remove_weight(const T& u, const T& v); /* 移除权重 */
void remove_vertex(const T& u); /* 移除顶点 */
void remove_edge(const T& u, const T& v); /* 移除边 */
int degree(const T& u); /* 求顶点的度数 */
int num_vertices(); /* 求图中顶点的总数 */
int num_edges(); /* 求图中边的总数*/
int largest_degree(); /* 求图中的最大度数 */
int get_weight(const T& u, const T& v); /* 得到某两个顶点之间边的权重 */
vector<T> get_vertices(); /* 得到图中所有顶点 */
map<T, int> get_neighbours(const T& u); /* 得到顶点u的所有边 */
void show();
void dft_recursion(const T& u, set<T>& visited, vector<T>& result); /* 深度优先遍历递归辅助函数 */
vector<T> depth_first_rec(const T& u); /* 深度优先遍历递归法 */
vector<T> depth_first_itr(const T& u); /* 深度优先遍历迭代法*/
vector<T> breadth_first(const T& u); /* 广度优先遍历迭代法 */
Graph<T> prim(T v); /* prim最小生成树算法 */
map<T, int> dijkstra(T start); /* dijkstra最短路径算法 */
vector<vector<T>> get_connected_components(); /* 获得图中的连通分量 */
void print_connected_components(const vector<vector<T>>& connected_components); /* 打印连通分量 */
};
由于图的函数声明除了最后两个函数其他的都在前四节中实现了,所以这里只放求连通分量和打印连通分量信息的代码(graph.hpp):
template <typename T> vector<vector<T>> Graph<T>::get_connected_components() {
set<T> visited_vertices;
vector<vector<T>> connected_components;
for (auto vertex : adj) {
// 对每一个未访问过的顶点进行深度优先遍历
if (visited_vertices.find(vertex.first) == visited_vertices.end()) {
stack<T> s;
s.push(vertex.first);
// 定义一个临时变量"connected_component"用来存储当前连通分量中的顶点
vector<T> connected_component;
// 深度优先遍历
while (!s.empty()) {
T u = s.top();
s.pop();
// 将未访问过的顶点放入连通分量"connected_component"中
if (visited_vertices.find(u) == visited_vertices.end()) {
connected_component.push_back(u);
visited_vertices.insert(u);
}
// 当前顶点未访问过的邻居入栈
for (auto neighbour : adj[u])
if (visited_vertices.find(neighbour.vertex) == visited_vertices.end())
s.push(neighbour.vertex);
}
// 将连通分量放到连通分量的集合"connected_components"中
connected_components.push_back(connected_component);
}
}
return connected_components;
}
template <typename T> void Graph<T>::print_connected_components(const vector<vector<T>>& connected_components ) {
int number = connected_components.size();
if(number == 1) cout << "该图是连通图,只有一个连通分量,就是其自身" << endl;
else if (number > 1){
cout << "图中共有" << number << "个连通分量" << endl;
for (unsigned i = 0; i < connected_components.size(); i++) {
cout << "第" << i + 1 << "个连通分量中的顶点分别为:";
for (unsigned j = 0; j < connected_components[i].size(); j++) {
cout << " " << connected_components[i][j];
}
cout << endl;
}
}
}
测试
测试案例(graph_testing.cpp):
void test05(Graph<int> g) {
vector<vector<int>> connected_components = g.get_connected_components();
g.print_connected_components(connected_components);
}
int main()
{
//Graph<char> g;
//g.add_vertex('A');
//g.add_vertex('B');
//g.add_vertex('C');
//g.add_vertex('D');
//g.add_vertex('E');
//g.add_vertex('F');
//g.add_vertex('G');
//g.add_edge('A', 'B', 7);
//g.add_edge('A', 'D', 5);
//g.add_edge('B', 'C', 8);
//g.add_edge('B', 'D', 9);
//g.add_edge('B', 'E', 7);
//g.add_edge('C', 'E', 5);
//g.add_edge('D', 'E', 15);
//g.add_edge('D', 'F', 6);
//g.add_edge('E', 'F', 8);
//g.add_edge('E', 'G', 9);
//g.add_edge('F', 'G', 11);
//g.add_vertex('H');
//g.add_edge('B', 'H', 9);
//g.add_edge('A', 'H', 10);
//g.add_edge('D', 'H', 11);
//g.add_edge('A', 'H', 12);
//g.remove_vertex('H');
//cout << "打印图中顶点及其邻接表的详细信息如下" << endl;
//g.show();
//cout << endl;
// test01(g);
// test02(g);
// test03(g);
// test04(g);
Graph<int> g;
g.add_vertex(0);
g.add_vertex(1);
g.add_vertex(2);
g.add_vertex(3);
g.add_vertex(4);
g.add_vertex(5);
g.add_vertex(6);
g.add_vertex(7);
g.add_vertex(8);
g.add_vertex(9);
g.add_vertex(10);
g.add_vertex(11);
g.add_vertex(12);
g.add_edge(0, 1 , 1);
g.add_edge(0, 2, 1);
g.add_edge(1, 2, 1);
g.add_edge(1, 3, 1);
g.add_edge(1, 4, 1);
g.add_edge(3, 6, 1);
g.add_edge(4, 5, 1);
g.add_edge(7, 8, 1);
g.add_edge(7, 9, 1);
g.add_edge(7, 12, 1);
g.add_edge(8, 9, 1);
g.add_edge(8, 12, 1);
g.add_edge(9, 10, 1);
g.add_edge(9, 11, 1);
g.add_edge(9, 12, 1);
g.add_edge(10, 11, 1);
cout << "打印图中顶点及其邻接表的详细信息如下" << endl;
g.show();
cout << endl;
test05(g);
return 0;
}
测试对应的图G如下,由于该图是无权无向图,所以main函数中的权重都设为1:
运行结果: