7-3 连通分量
连通分量(Connected Components)
连通分量(Connected Components)是无向图(Undirected Graph)中的一个重要概念。一个连通分量是图中顶点的一个极大子集(Maximal Subset),其中任意两个顶点之间都存在路径(Path)。一个图可能包含多个连通分量,每个连通分量内部互相可达,不同连通分量之间不可达。
寻找所有连通分量有助于理解图的整体结构,广泛应用于网络分析(Network Analysis)、图像分割(Image Segmentation)和聚类(Clustering)等领域。
下面使用一个非连通图作为示例:
0 — 1 — 2 5 — 6 8
| |
3 — 4 7
邻接表(Adjacency List)表示为:{0:[1,3], 1:[0,2], 2:[1,7], 3:[0,4], 4:[3], 5:[6], 6:[5], 7:[2], 8:[]}
该图包含 3 个连通分量:{0,1,2,3,4,7}、{5,6}、{8}。
使用 DFS 找连通分量
深度优先搜索(DFS, Depth-First Search)是找连通分量的经典方法。基本思路是:遍历所有顶点,对于每个未访问的顶点,启动一次 DFS,DFS 过程中访问到的所有顶点构成一个连通分量。
具体步骤如下:
- 维护一个
visited数组,记录每个顶点是否已被访问 - 从顶点 0 开始,依次检查每个顶点
- 如果顶点未被访问,则从该顶点启动 DFS,所有被访问到的顶点属于同一个连通分量
- DFS 递归访问当前顶点的所有未访问邻居
- DFS 结束后,连通分量编号加一
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
// DFS to visit all vertices in one connected component
void dfs(const vector<vector<int>>& graph, int node, vector<bool>& visited, vector<int>& component) {
visited[node] = true;
component.push_back(node);
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
dfs(graph, neighbor, visited, component);
}
}
}
int main() {
// Adjacency list representation of the disconnected graph
vector<vector<int>> graph = {
{1, 3}, // 0
{0, 2}, // 1
{1, 7}, // 2
{0, 4}, // 3
{3}, // 4
{6}, // 5
{5}, // 6
{2}, // 7
{} // 8 (isolated vertex)
};
int n = graph.size();
vector<bool> visited(n, false);
cout << "DFS Connected Components:" << endl;
int componentId = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
vector<int> component;
dfs(graph, i, visited, component);
cout << "Component " << componentId << ": {";
for (int j = 0; j < (int)component.size(); j++) {
cout << component[j];
if (j < (int)component.size() - 1) cout << ",";
}
cout << "}" << endl;
componentId++;
}
}
return 0;
}
C 实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_VERTICES 20
int component[MAX_VERTICES]; // Store vertices in current component
int compSize; // Current component size
// DFS to visit all vertices in one connected component
void dfs(int graph[][MAX_VERTICES], int graphSize[], int node, bool* visited) {
visited[node] = true;
component[compSize++] = node;
for (int i = 0; i < graphSize[node]; i++) {
int neighbor = graph[node][i];
if (!visited[neighbor]) {
dfs(graph, graphSize, neighbor, visited);
}
}
}
int main() {
// Build adjacency list using 2D array + size array
int graph[MAX_VERTICES][MAX_VERTICES] = {0};
int graphSize[MAX_VERTICES] = {0};
// Edge list
int edges[][2] = {{0,1},{0,3},{1,2},{2,7},{3,4},{5,6}};
int edgeCount = 6;
int n = 9; // Total vertices
// Build adjacency list
for (int i = 0; i < edgeCount; i++) {
int u = edges[i][0], v = edges[i][1];
graph[u][graphSize[u]++] = v;
graph[v][graphSize[v]++] = u;
}
// Vertex 8 has no edges (isolated)
bool visited[MAX_VERTICES] = {false};
printf("DFS Connected Components:\n");
int componentId = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
compSize = 0;
dfs(graph, graphSize, i, visited);
printf("Component %d: {", componentId);
for (int j = 0; j < compSize; j++) {
printf("%d", component[j]);
if (j < compSize - 1) printf(",");
}
printf("}\n");
componentId++;
}
}
return 0;
}
Python 实现
# DFS to visit all vertices in one connected component
def dfs(graph, node, visited, component):
visited[node] = True
component.append(node)
for neighbor in graph[node]:
if not visited[neighbor]:
dfs(graph, neighbor, visited, component)
def find_components_dfs(graph):
n = len(graph)
visited = [False] * n
components = []
for i in range(n):
if not visited[i]:
component = []
dfs(graph, i, visited, component)
components.append(component)
return components
# Adjacency list representation
graph = {
0: [1, 3],
1: [0, 2],
2: [1, 7],
3: [0, 4],
4: [3],
5: [6],
6: [5],
7: [2],
8: [] # Isolated vertex
}
print("DFS Connected Components:")
components = find_components_dfs(graph)
for i, comp in enumerate(components):
print(f"Component {i}: {{{','.join(map(str, comp))}}}")
Go 实现
package main
import "fmt"
// dfs visits all vertices in one connected component
func dfs(graph [][]int, node int, visited []bool, component *[]int) {
visited[node] = true
*component = append(*component, node)
for _, neighbor := range graph[node] {
if !visited[neighbor] {
dfs(graph, neighbor, visited, component)
}
}
}
func main() {
// Adjacency list representation
graph := [][]int{
{1, 3}, // 0
{0, 2}, // 1
{1, 7}, // 2
{0, 4}, // 3
{3}, // 4
{6}, // 5
{5}, // 6
{2}, // 7
{}, // 8 (isolated vertex)
}
n := len(graph)
visited := make([]bool, n)
fmt.Println("DFS Connected Components:")
componentID := 0
for i := 0; i < n; i++ {
if !visited[i] {
var component []int
dfs(graph, i, visited, &component)
fmt.Printf("Component %d: {", componentID)
for j, v := range component {
if j > 0 {
fmt.Print(",")
}
fmt.Print(v)
}
fmt.Println("}")
componentID++
}
}
}
Go 版本使用切片 []int 存储邻接表,通过指向切片的指针 *[]int 在 DFS 递归中累积连通分量的顶点。visited 数组在 dfs 函数间共享,无需返回值。
以上四个版本都使用相同的 DFS 策略:外层循环遍历所有顶点,遇到未访问的顶点就启动 DFS,DFS 递归访问所有可达的邻居,这些顶点构成一个连通分量。
运行该程序将输出:
DFS Connected Components:
Component 0: {0,1,2,7,3,4}
Component 1: {5,6}
Component 2: {8}
注意 DFS 遍历的顺序与邻接表中邻居的排列有关,因此 Component 0 的顶点顺序可能是 {0,1,2,7,3,4} 而非 {0,1,2,3,4,7},但它们包含的顶点集合是一致的。
使用 BFS 找连通分量
广度优先搜索(BFS, Breadth-First Search)同样可以用来找连通分量,思路与 DFS 完全一致,只是将遍历方式从深度优先改为广度优先。BFS 使用队列(Queue)逐层访问邻居,适合需要按距离层次遍历的场景。
C++ 实现
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// BFS to visit all vertices in one connected component
vector<int> bfs(const vector<vector<int>>& graph, int start, vector<bool>& visited) {
vector<int> component;
queue<int> q;
visited[start] = true;
q.push(start);
while (!q.empty()) {
int node = q.front();
q.pop();
component.push_back(node);
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
return component;
}
int main() {
vector<vector<int>> graph = {
{1, 3}, // 0
{0, 2}, // 1
{1, 7}, // 2
{0, 4}, // 3
{3}, // 4
{6}, // 5
{5}, // 6
{2}, // 7
{} // 8
};
int n = graph.size();
vector<bool> visited(n, false);
cout << "BFS Connected Components:" << endl;
int componentId = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
vector<int> component = bfs(graph, i, visited);
cout << "Component " << componentId << ": {";
for (int j = 0; j < (int)component.size(); j++) {
cout << component[j];
if (j < (int)component.size() - 1) cout << ",";
}
cout << "}" << endl;
componentId++;
}
}
return 0;
}
C 实现
#include <stdio.h>
#include <stdbool.h>
#define MAX_VERTICES 20
// BFS to visit all vertices in one connected component
void bfs(int graph[][MAX_VERTICES], int graphSize[], int start, bool* visited, int* component, int* compSize) {
int queue[MAX_VERTICES];
int front = 0, rear = 0;
visited[start] = true;
queue[rear++] = start;
*compSize = 0;
while (front < rear) {
int node = queue[front++];
component[(*compSize)++] = node;
for (int i = 0; i < graphSize[node]; i++) {
int neighbor = graph[node][i];
if (!visited[neighbor]) {
visited[neighbor] = true;
queue[rear++] = neighbor;
}
}
}
}
int main() {
int graph[MAX_VERTICES][MAX_VERTICES] = {0};
int graphSize[MAX_VERTICES] = {0};
int edges[][2] = {{0,1},{0,3},{1,2},{2,7},{3,4},{5,6}};
int edgeCount = 6;
int n = 9;
for (int i = 0; i < edgeCount; i++) {
int u = edges[i][0], v = edges[i][1];
graph[u][graphSize[u]++] = v;
graph[v][graphSize[v]++] = u;
}
bool visited[MAX_VERTICES] = {false};
int component[MAX_VERTICES];
int compSize;
printf("BFS Connected Components:\n");
int componentId = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
bfs(graph, graphSize, i, visited, component, &compSize);
printf("Component %d: {", componentId);
for (int j = 0; j < compSize; j++) {
printf("%d", component[j]);
if (j < compSize - 1) printf(",");
}
printf("}\n");
componentId++;
}
}
return 0;
}
Python 实现
from collections import deque
# BFS to visit all vertices in one connected component
def bfs(graph, start, visited):
component = []
queue = deque([start])
visited[start] = True
while queue:
node = queue.popleft()
component.append(node)
for neighbor in graph[node]:
if not visited[neighbor]:
visited[neighbor] = True
queue.append(neighbor)
return component
def find_components_bfs(graph):
n = len(graph)
visited = [False] * n
components = []
for i in range(n):
if not visited[i]:
component = bfs(graph, i, visited)
components.append(component)
return components
graph = {
0: [1, 3],
1: [0, 2],
2: [1, 7],
3: [0, 4],
4: [3],
5: [6],
6: [5],
7: [2],
8: []
}
print("BFS Connected Components:")
components = find_components_bfs(graph)
for i, comp in enumerate(components):
print(f"Component {i}: {{{','.join(map(str, comp))}}}")
Go 实现
package main
import "fmt"
// bfs visits all vertices in one connected component
func bfs(graph [][]int, start int, visited []bool) []int {
var component []int
queue := []int{start}
visited[start] = true
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
component = append(component, node)
for _, neighbor := range graph[node] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, neighbor)
}
}
}
return component
}
func main() {
graph := [][]int{
{1, 3}, // 0
{0, 2}, // 1
{1, 7}, // 2
{0, 4}, // 3
{3}, // 4
{6}, // 5
{5}, // 6
{2}, // 7
{}, // 8
}
n := len(graph)
visited := make([]bool, n)
fmt.Println("BFS Connected Components:")
componentID := 0
for i := 0; i < n; i++ {
if !visited[i] {
component := bfs(graph, i, visited)
fmt.Printf("Component %d: {", componentID)
for j, v := range component {
if j > 0 {
fmt.Print(",")
}
fmt.Print(v)
}
fmt.Println("}")
componentID++
}
}
}
Go 版本的 BFS 使用切片模拟队列:通过 queue[0] 取队首元素,queue[1:] 弹出队首。虽然这不是最高效的队列实现(每次弹出都会创建新切片),但对于小规模图完全够用,且代码简洁。
BFS 与 DFS 的区别仅在于遍历策略:DFS 使用栈(递归调用栈),BFS 使用队列。两者找到的连通分量集合相同,但顶点的访问顺序不同。BFS 按层次访问,因此离起点近的顶点会先被访问到。
运行该程序将输出:
BFS Connected Components:
Component 0: {0,1,3,2,4,7}
Component 1: {5,6}
Component 2: {8}
使用并查集(Union-Find)找连通分量
并查集(Union-Find / Disjoint Set Union, DSU)是另一种高效找连通分量的方法。它维护一组不相交的集合,支持两种核心操作:
- 查找(Find):确定某个元素属于哪个集合,即找到该集合的代表元素(根节点)
- 合并(Union):将两个元素所在的集合合并为一个集合
通过路径压缩(Path Compression)和按秩合并(Union by Rank)两种优化,并查集的每次操作时间复杂度接近 O(1)。
具体步骤如下:
- 初始化时,每个顶点自成一个集合(
parent[i] = i) - 遍历所有边,对每条边的两个端点执行 Union 操作
- 最终统计有多少个不同的根节点,即为连通分量的数量
- 将具有相同根节点的顶点归为一组
C++ 实现
#include <iostream>
#include <vector>
#include <map>
using namespace std;
class UnionFind {
private:
vector<int> parent;
vector<int> rank_;
public:
// Initialize: each vertex is its own parent (separate set)
UnionFind(int n) {
parent.resize(n);
rank_.resize(n, 0);
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
// Find with path compression
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // Path compression
}
return parent[x];
}
// Union by rank
void unionSets(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return; // Already in same set
// Attach smaller rank tree under root of larger rank tree
if (rank_[rootX] < rank_[rootY]) {
parent[rootX] = rootY;
} else if (rank_[rootX] > rank_[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank_[rootX]++;
}
}
};
int main() {
int n = 9;
UnionFind uf(n);
// Edge list
vector<pair<int,int>> edges = {
{0,1}, {0,3}, {1,2}, {2,7}, {3,4}, {5,6}
};
// Union all edges
for (auto& e : edges) {
uf.unionSets(e.first, e.second);
}
// Group vertices by root
map<int, vector<int>> components;
for (int i = 0; i < n; i++) {
components[uf.find(i)].push_back(i);
}
cout << "Union-Find Connected Components:" << endl;
int componentId = 0;
for (auto& [root, vertices] : components) {
cout << "Component " << componentId << ": {";
for (int j = 0; j < (int)vertices.size(); j++) {
cout << vertices[j];
if (j < (int)vertices.size() - 1) cout << ",";
}
cout << "}" << endl;
componentId++;
}
return 0;
}
C 实现
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int* parent;
int* rank;
int n;
} UnionFind;
// Initialize Union-Find structure
UnionFind* createUF(int n) {
UnionFind* uf = (UnionFind*)malloc(sizeof(UnionFind));
uf->n = n;
uf->parent = (int*)malloc(n * sizeof(int));
uf->rank = (int*)calloc(n, sizeof(int));
for (int i = 0; i < n; i++) {
uf->parent[i] = i;
}
return uf;
}
// Find with path compression
int find(UnionFind* uf, int x) {
if (uf->parent[x] != x) {
uf->parent[x] = find(uf, uf->parent[x]);
}
return uf->parent[x];
}
// Union by rank
void unionSets(UnionFind* uf, int x, int y) {
int rootX = find(uf, x);
int rootY = find(uf, y);
if (rootX == rootY) return;
if (uf->rank[rootX] < uf->rank[rootY]) {
uf->parent[rootX] = rootY;
} else if (uf->rank[rootX] > uf->rank[rootY]) {
uf->parent[rootY] = rootX;
} else {
uf->parent[rootY] = rootX;
uf->rank[rootX]++;
}
}
// Free Union-Find structure
void freeUF(UnionFind* uf) {
free(uf->parent);
free(uf->rank);
free(uf);
}
int main() {
int n = 9;
UnionFind* uf = createUF(n);
int edges[][2] = {{0,1},{0,3},{1,2},{2,7},{3,4},{5,6}};
int edgeCount = 6;
// Union all edges
for (int i = 0; i < edgeCount; i++) {
unionSets(uf, edges[i][0], edges[i][1]);
}
// Group vertices by root
int roots[MAX_VERTICES];
int rootCount = 0;
int vertexToComponent[MAX_VERTICES];
for (int i = 0; i < n; i++) {
int r = find(uf, i);
int found = -1;
for (int j = 0; j < rootCount; j++) {
if (roots[j] == r) {
found = j;
break;
}
}
if (found == -1) {
roots[rootCount] = r;
vertexToComponent[i] = rootCount;
rootCount++;
} else {
vertexToComponent[i] = found;
}
}
printf("Union-Find Connected Components:\n");
for (int c = 0; c < rootCount; c++) {
printf("Component %d: {", c);
int first = 1;
for (int i = 0; i < n; i++) {
if (vertexToComponent[i] == c) {
if (!first) printf(",");
printf("%d", i);
first = 0;
}
}
printf("}\n");
}
freeUF(uf);
return 0;
}
Python 实现
class UnionFind:
def __init__(self, n):
# Each vertex is its own parent initially
self.parent = list(range(n))
self.rank = [0] * n
# Find with path compression
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
# Union by rank
def union(self, x, y):
root_x = self.find(x)
root_y = self.find(y)
if root_x == root_y:
return
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
n = 9
uf = UnionFind(n)
edges = [(0,1), (0,3), (1,2), (2,7), (3,4), (5,6)]
# Union all edges
for u, v in edges:
uf.union(u, v)
# Group vertices by root
from collections import defaultdict
components = defaultdict(list)
for i in range(n):
components[uf.find(i)].append(i)
print("Union-Find Connected Components:")
for i, (root, vertices) in enumerate(components.items()):
print(f"Component {i}: {{{','.join(map(str, vertices))}}}")
Go 实现
package main
import "fmt"
// UnionFind represents the disjoint set union data structure
type UnionFind struct {
parent []int
rank []int
}
// NewUnionFind creates a new Union-Find with n elements
func NewUnionFind(n int) *UnionFind {
uf := &UnionFind{
parent: make([]int, n),
rank: make([]int, n),
}
for i := 0; i < n; i++ {
uf.parent[i] = i
}
return uf
}
// Find with path compression
func (uf *UnionFind) Find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.Find(uf.parent[x])
}
return uf.parent[x]
}
// Union by rank
func (uf *UnionFind) Union(x, y int) {
rootX := uf.Find(x)
rootY := uf.Find(y)
if rootX == rootY {
return
}
if uf.rank[rootX] < uf.rank[rootY] {
uf.parent[rootX] = rootY
} else if uf.rank[rootX] > uf.rank[rootY] {
uf.parent[rootY] = rootX
} else {
uf.parent[rootY] = rootX
uf.rank[rootX]++
}
}
func main() {
n := 9
uf := NewUnionFind(n)
edges := [][2]int{{0, 1}, {0, 3}, {1, 2}, {2, 7}, {3, 4}, {5, 6}}
// Union all edges
for _, e := range edges {
uf.Union(e[0], e[1])
}
// Group vertices by root
type void struct{}
rootSet := make(map[int]void)
for i := 0; i < n; i++ {
rootSet[uf.Find(i)] = void{}
}
// Build component map
components := make(map[int][]int)
for i := 0; i < n; i++ {
root := uf.Find(i)
components[root] = append(components[root], i)
}
fmt.Println("Union-Find Connected Components:")
componentID := 0
for _, vertices := range components {
fmt.Printf("Component %d: {", componentID)
for j, v := range vertices {
if j > 0 {
fmt.Print(",")
}
fmt.Print(v)
}
fmt.Println("}")
componentID++
}
}
Go 版本的并查集使用结构体 UnionFind 封装 parent 和 rank 切片。Find 方法通过递归实现路径压缩,Union 方法使用按秩合并策略。Go 的 map 天然适合按根节点分组。
并查集方法的优势在于:不需要显式构建完整的邻接表,只需要遍历所有边即可。对于边稀疏的图,这种方法更加高效。路径压缩让 find 操作几乎接近 O(1),按秩合并确保树的高度始终较低。
运行该程序将输出:
Union-Find Connected Components:
Component 0: {0,1,2,3,4,7}
Component 1: {5,6}
Component 2: {8}
完整实现
以下程序将三种方法整合在一起,对同一个非连通图分别使用 DFS、BFS 和并查集(Union-Find)查找连通分量,并对比输出结果。
C++ 实现
#include <iostream>
#include <vector>
#include <queue>
#include <map>
using namespace std;
// ============ DFS Method ============
void dfs(const vector<vector<int>>& graph, int node, vector<bool>& visited, vector<int>& component) {
visited[node] = true;
component.push_back(node);
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
dfs(graph, neighbor, visited, component);
}
}
}
vector<vector<int>> findComponentsDFS(const vector<vector<int>>& graph) {
int n = graph.size();
vector<bool> visited(n, false);
vector<vector<int>> components;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
vector<int> component;
dfs(graph, i, visited, component);
components.push_back(component);
}
}
return components;
}
// ============ BFS Method ============
vector<vector<int>> findComponentsBFS(const vector<vector<int>>& graph) {
int n = graph.size();
vector<bool> visited(n, false);
vector<vector<int>> components;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
vector<int> component;
queue<int> q;
visited[i] = true;
q.push(i);
while (!q.empty()) {
int node = q.front();
q.pop();
component.push_back(node);
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
components.push_back(component);
}
}
return components;
}
// ============ Union-Find Method ============
class UnionFind {
private:
vector<int> parent;
vector<int> rank_;
public:
UnionFind(int n) : parent(n), rank_(n, 0) {
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
void unionSets(int x, int y) {
int rx = find(x), ry = find(y);
if (rx == ry) return;
if (rank_[rx] < rank_[ry]) parent[rx] = ry;
else if (rank_[rx] > rank_[ry]) parent[ry] = rx;
else { parent[ry] = rx; rank_[rx]++; }
}
};
vector<vector<int>> findComponentsUF(int n, const vector<pair<int,int>>& edges) {
UnionFind uf(n);
for (auto& e : edges) uf.unionSets(e.first, e.second);
map<int, vector<int>> groups;
for (int i = 0; i < n; i++) groups[uf.find(i)].push_back(i);
vector<vector<int>> components;
for (auto& [root, verts] : groups) components.push_back(verts);
return components;
}
// ============ Helper ============
void printComponents(const string& method, const vector<vector<int>>& components) {
cout << method << ":" << endl;
for (int i = 0; i < (int)components.size(); i++) {
cout << " Component " << i << ": {";
for (int j = 0; j < (int)components[i].size(); j++) {
cout << components[i][j];
if (j < (int)components[i].size() - 1) cout << ",";
}
cout << "}" << endl;
}
}
int main() {
vector<vector<int>> graph = {
{1, 3}, {0, 2}, {1, 7}, {0, 4}, {3}, {6}, {5}, {2}, {}
};
vector<pair<int,int>> edges = {
{0,1}, {0,3}, {1,2}, {2,7}, {3,4}, {5,6}
};
int n = graph.size();
cout << "=== Comparing Three Methods ===" << endl;
cout << "Graph: 9 vertices, 6 edges" << endl;
cout << "Expected: 3 connected components" << endl << endl;
printComponents("DFS Method", findComponentsDFS(graph));
cout << endl;
printComponents("BFS Method", findComponentsBFS(graph));
cout << endl;
printComponents("Union-Find Method", findComponentsUF(n, edges));
return 0;
}
C 实现
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#define MAX_V 20
// ============ DFS Method ============
int compBuf[MAX_V], compLen;
void dfs(int g[][MAX_V], int gs[], int node, bool* vis) {
vis[node] = true;
compBuf[compLen++] = node;
for (int i = 0; i < gs[node]; i++) {
if (!vis[g[node][i]]) dfs(g, gs, g[node][i], vis);
}
}
// ============ BFS Method ============
void bfs(int g[][MAX_V], int gs[], int start, bool* vis, int* comp, int* clen) {
int q[MAX_V], front = 0, rear = 0;
vis[start] = true;
q[rear++] = start;
*clen = 0;
while (front < rear) {
int node = q[front++];
comp[(*clen)++] = node;
for (int i = 0; i < gs[node]; i++) {
int nb = g[node][i];
if (!vis[nb]) { vis[nb] = true; q[rear++] = nb; }
}
}
}
// ============ Union-Find Method ============
int ufParent[MAX_V], ufRank[MAX_V];
int ufFind(int x) {
if (ufParent[x] != x) ufParent[x] = ufFind(ufParent[x]);
return ufParent[x];
}
void ufUnion(int x, int y) {
int rx = ufFind(x), ry = ufFind(y);
if (rx == ry) return;
if (ufRank[rx] < ufRank[ry]) ufParent[rx] = ry;
else if (ufRank[rx] > ufRank[ry]) ufParent[ry] = rx;
else { ufParent[ry] = rx; ufRank[rx]++; }
}
// ============ Print Helper ============
void printComp(const char* method, int comps[][MAX_V], int compSizes[], int count) {
printf("%s:\n", method);
for (int c = 0; c < count; c++) {
printf(" Component %d: {", c);
for (int j = 0; j < compSizes[c]; j++) {
printf("%d", comps[c][j]);
if (j < compSizes[c] - 1) printf(",");
}
printf("}\n");
}
}
int main() {
int g[MAX_V][MAX_V] = {0}, gs[MAX_V] = {0};
int edges[][2] = {{0,1},{0,3},{1,2},{2,7},{3,4},{5,6}};
int n = 9, ec = 6;
for (int i = 0; i < ec; i++) {
int u = edges[i][0], v = edges[i][1];
g[u][gs[u]++] = v;
g[v][gs[v]++] = u;
}
printf("=== Comparing Three Methods ===\n");
printf("Graph: 9 vertices, 6 edges\n");
printf("Expected: 3 connected components\n\n");
// DFS
bool vis[MAX_V] = {false};
int comps[MAX_V][MAX_V], csizes[MAX_V], ccnt = 0;
for (int i = 0; i < n; i++) {
if (!vis[i]) {
compLen = 0;
dfs(g, gs, i, vis);
csizes[ccnt] = compLen;
for (int j = 0; j < compLen; j++) comps[ccnt][j] = compBuf[j];
ccnt++;
}
}
printComp("DFS Method", comps, csizes, ccnt);
printf("\n");
// BFS
for (int i = 0; i < n; i++) vis[i] = false;
ccnt = 0;
for (int i = 0; i < n; i++) {
if (!vis[i]) {
int clen;
bfs(g, gs, i, vis, comps[ccnt], &clen);
csizes[ccnt] = clen;
ccnt++;
}
}
printComp("BFS Method", comps, csizes, ccnt);
printf("\n");
// Union-Find
for (int i = 0; i < n; i++) { ufParent[i] = i; ufRank[i] = 0; }
for (int i = 0; i < ec; i++) ufUnion(edges[i][0], edges[i][1]);
// Group by root
int roots[MAX_V], rootCnt = 0, v2c[MAX_V];
for (int i = 0; i < n; i++) {
int r = ufFind(i), found = -1;
for (int j = 0; j < rootCnt; j++) {
if (roots[j] == r) { found = j; break; }
}
if (found == -1) { roots[rootCnt] = r; v2c[i] = rootCnt; rootCnt++; }
else { v2c[i] = found; }
}
ccnt = rootCnt;
for (int c = 0; c < ccnt; c++) csizes[c] = 0;
for (int i = 0; i < n; i++) {
comps[v2c[i]][csizes[v2c[i]]++] = i;
}
printComp("Union-Find Method", comps, csizes, ccnt);
return 0;
}
Python 实现
from collections import defaultdict, deque
def find_components_dfs(graph):
n = len(graph)
visited = [False] * n
components = []
def dfs(node):
visited[node] = True
comp.append(node)
for nb in graph[node]:
if not visited[nb]:
dfs(nb)
for i in range(n):
if not visited[i]:
comp = []
dfs(i)
components.append(comp)
return components
def find_components_bfs(graph):
n = len(graph)
visited = [False] * n
components = []
for i in range(n):
if not visited[i]:
comp = []
q = deque([i])
visited[i] = True
while q:
node = q.popleft()
comp.append(node)
for nb in graph[node]:
if not visited[nb]:
visited[nb] = True
q.append(nb)
components.append(comp)
return components
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
rx, ry = self.find(x), self.find(y)
if rx == ry:
return
if self.rank[rx] < self.rank[ry]:
self.parent[rx] = ry
elif self.rank[rx] > self.rank[ry]:
self.parent[ry] = rx
else:
self.parent[ry] = rx
self.rank[rx] += 1
def find_components_uf(n, edges):
uf = UnionFind(n)
for u, v in edges:
uf.union(u, v)
groups = defaultdict(list)
for i in range(n):
groups[uf.find(i)].append(i)
return list(groups.values())
def print_components(method, components):
print(f"{method}:")
for i, comp in enumerate(components):
print(f" Component {i}: {{{','.join(map(str, comp))}}}")
graph = {0:[1,3], 1:[0,2], 2:[1,7], 3:[0,4], 4:[3], 5:[6], 6:[5], 7:[2], 8:[]}
edges = [(0,1), (0,3), (1,2), (2,7), (3,4), (5,6)]
n = 9
print("=== Comparing Three Methods ===")
print("Graph: 9 vertices, 6 edges")
print("Expected: 3 connected components\n")
print_components("DFS Method", find_components_dfs(graph))
print()
print_components("BFS Method", find_components_bfs(graph))
print()
print_components("Union-Find Method", find_components_uf(n, edges))
Go 实现
package main
import "fmt"
// ============ DFS Method ============
func dfsFind(graph [][]int) [][]int {
n := len(graph)
visited := make([]bool, n)
var components [][]int
var dfs func(node int, comp *[]int)
dfs = func(node int, comp *[]int) {
visited[node] = true
*comp = append(*comp, node)
for _, nb := range graph[node] {
if !visited[nb] {
dfs(nb, comp)
}
}
}
for i := 0; i < n; i++ {
if !visited[i] {
var comp []int
dfs(i, &comp)
components = append(components, comp)
}
}
return components
}
// ============ BFS Method ============
func bfsFind(graph [][]int) [][]int {
n := len(graph)
visited := make([]bool, n)
var components [][]int
for i := 0; i < n; i++ {
if !visited[i] {
var comp []int
queue := []int{i}
visited[i] = true
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
comp = append(comp, node)
for _, nb := range graph[node] {
if !visited[nb] {
visited[nb] = true
queue = append(queue, nb)
}
}
}
components = append(components, comp)
}
}
return components
}
// ============ Union-Find Method ============
type UnionFind struct {
parent []int
rank []int
}
func NewUF(n int) *UnionFind {
uf := &UnionFind{parent: make([]int, n), rank: make([]int, n)}
for i := range uf.parent {
uf.parent[i] = i
}
return uf
}
func (uf *UnionFind) Find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.Find(uf.parent[x])
}
return uf.parent[x]
}
func (uf *UnionFind) Union(x, y int) {
rx, ry := uf.Find(x), uf.Find(y)
if rx == ry {
return
}
if uf.rank[rx] < uf.rank[ry] {
uf.parent[rx] = ry
} else if uf.rank[rx] > uf.rank[ry] {
uf.parent[ry] = rx
} else {
uf.parent[ry] = rx
uf.rank[rx]++
}
}
func ufFind(n int, edges [][2]int) [][]int {
uf := NewUF(n)
for _, e := range edges {
uf.Union(e[0], e[1])
}
groups := make(map[int][]int)
for i := 0; i < n; i++ {
r := uf.Find(i)
groups[r] = append(groups[r], i)
}
var components [][]int
for _, verts := range groups {
components = append(components, verts)
}
return components
}
// ============ Helper ============
func printComponents(method string, components [][]int) {
fmt.Printf("%s:\n", method)
for i, comp := range components {
fmt.Printf(" Component %d: {", i)
for j, v := range comp {
if j > 0 {
fmt.Print(",")
}
fmt.Print(v)
}
fmt.Println("}")
}
}
func main() {
graph := [][]int{
{1, 3}, {0, 2}, {1, 7}, {0, 4}, {3}, {6}, {5}, {2}, {},
}
edges := [][2]int{{0, 1}, {0, 3}, {1, 2}, {2, 7}, {3, 4}, {5, 6}}
n := 9
fmt.Println("=== Comparing Three Methods ===")
fmt.Println("Graph: 9 vertices, 6 edges")
fmt.Println("Expected: 3 connected components")
fmt.Println()
printComponents("DFS Method", dfsFind(graph))
fmt.Println()
printComponents("BFS Method", bfsFind(graph))
fmt.Println()
printComponents("Union-Find Method", ufFind(n, edges))
}
Go 版本将三种方法封装为独立函数:dfsFind、bfsFind 和 ufFind,各自返回 [][]int 类型的连通分量列表。printComponents 辅助函数统一输出格式,方便对比三种方法的结果。
三种方法的核心差异在于数据结构的选择:DFS 使用递归调用栈,BFS 使用显式队列,并查集使用父指针数组。对于同一个图,三种方法找到的连通分量集合完全一致,只是顶点的访问顺序可能不同。
运行该程序将输出:
=== Comparing Three Methods ===
Graph: 9 vertices, 6 edges
Expected: 3 connected components
DFS Method:
Component 0: {0,1,2,7,3,4}
Component 1: {5,6}
Component 2: {8}
BFS Method:
Component 0: {0,1,3,2,4,7}
Component 1: {5,6}
Component 2: {8}
Union-Find Method:
Component 0: {0,1,2,3,4,7}
Component 1: {5,6}
Component 2: {8}
三种方法都正确识别出 3 个连通分量。DFS 和 BFS 的顶点顺序受遍历策略影响,而并查集的顶点顺序取决于 map 的迭代顺序。但无论顺序如何,每个连通分量包含的顶点集合完全相同。
连通分量的性质
时间复杂度
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| DFS | O(V + E) | O(V) | 递归实现简洁,适合一般图 |
| BFS | O(V + E) | O(V) | 需要按层次遍历时使用 |
| 并查集(Union-Find) | O(E * alpha(V)) 约 O(E) | O(V) | 动态加边、在线查询连通性 |
其中 V 是顶点数(Vertex),E 是边数(Edge),alpha 是反阿克曼函数(Inverse Ackermann Function),在实际应用中可视为常数(小于 5)。
方法对比
- DFS / BFS:需要先构建邻接表,然后遍历所有顶点。适合一次性查找所有连通分量的场景。时间复杂度为 O(V + E),是遍历图的最优复杂度。
- 并查集:只需要遍历所有边即可,不需要显式存储邻接表。特别适合动态加边的场景——当边逐步加入时,可以实时查询两个顶点是否在同一个连通分量中。路径压缩和按秩合并使得每次操作接近 O(1)。
应用场景
- 网络分析(Network Analysis):在社交网络中,连通分量代表一个个社群(Community),分析连通分量有助于理解网络的拓扑结构
- 图像分割(Image Segmentation):将图像的像素视为图的顶点,相邻且颜色相近的像素之间连边,连通分量即为分割出的区域
- 聚类(Clustering):将数据点视为图的顶点,相似度高的点之间连边,连通分量即为聚类结果
- 迷宫生成与求解:连通分量可以判断迷宫中两点之间是否存在通路
- 电路设计:判断电路板上的元件是否连通,检测孤立回路

浙公网安备 33010602011771号