【图的连通性】【并查集】【拓扑排序】
在图论中,不同类型的图(无向图和有向图)需要使用不同的算法和数据结构来处理它们的特性和问题。这里我们将讨论如何使用并查集来解决无向图的连通性问题,以及如何使用深度优先搜索(DFS)、广度优先搜索(BFS)和拓扑排序来解决有向图中的依赖性问题。
无向图的连通性:并查集
对于无向图的连通性问题,并查集(Union-Find)是一个非常有效的数据结构。它可以用于快速合并集合和查找元素所属的集合,从而帮助我们判断两个节点是否在同一个连通分量中。
示例:判断无向图的连通性
#include <vector>
#include <iostream>
class UnionFind {
public:
UnionFind(int n) {
parent.resize(n);
rank.resize(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];
}
bool unionSet(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) {
return false; // x 和 y 已经在同一个集合中
}
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
return true;
}
private:
std::vector<int> parent;
std::vector<int> rank;
};
int main() {
int n = 5; // 节点数
UnionFind uf(n);
// 添加边
std::vector<std::pair<int, int>> edges = {{0, 1}, {1, 2}, {3, 4}};
for (const auto& edge : edges) {
uf.unionSet(edge.first, edge.second);
}
// 检查连通性
std::cout << "0 and 2 are connected: " << (uf.find(0) == uf.find(2)) << std::endl; // 输出 1 (true)
std::cout << "0 and 4 are connected: " << (uf.find(0) == uf.find(4)) << std::endl; // 输出 0 (false)
return 0;
}
有向图的依赖性:DFS、BFS、拓扑排序
对于有向图的依赖性问题,例如检测是否存在环、确定任务的执行顺序等,我们通常使用深度优先搜索(DFS)、广度优先搜索(BFS)以及拓扑排序。
拓扑排序
拓扑排序是一种对有向无环图(DAG)进行排序的方法,使得对于每条边 ( u \to v ),顶点 ( u ) 在排序后序列中出现在顶点 ( v ) 之前。
使用 DFS 进行拓扑排序
#include <vector>
#include <stack>
#include <iostream>
class Graph {
public:
Graph(int vertices) : adj(vertices), V(vertices) {}
void addEdge(int u, int v) {
adj[u].push_back(v);
}
void topologicalSort() {
std::vector<bool> visited(V, false);
std::stack<int> Stack;
for (int i = 0; i < V; i++) {
if (!visited[i]) {
topologicalSortUtil(i, visited, Stack);
}
}
// 打印拓扑排序
while (!Stack.empty()) {
std::cout << Stack.top() << " ";
Stack.pop();
}
std::cout << std::endl;
}
private:
int V;
std::vector<std::vector<int>> adj;
void topologicalSortUtil(int v, std::vector<bool>& visited, std::stack<int>& Stack) {
visited[v] = true;
for (int i : adj[v]) {
if (!visited[i]) {
topologicalSortUtil(i, visited, Stack);
}
}
Stack.push(v);
}
};
int main() {
Graph g(6);
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
std::cout << "拓扑排序结果: ";
g.topologicalSort();
return 0;
}
使用 Kahn 算法(BFS)进行拓扑排序
#include <vector>
#include <queue>
#include <iostream>
class Graph {
public:
Graph(int vertices) : adj(vertices), V(vertices) {}
void addEdge(int u, int v) {
adj[u].push_back(v);
}
void topologicalSort() {
std::vector<int> in_degree(V, 0);
for (int u = 0; u < V; u++) {
for (int v : adj[u]) {
in_degree[v]++;
}
}
std::queue<int> q;
for (int i = 0; i < V; i++) {
if (in_degree[i] == 0) {
q.push(i);
}
}
int count = 0;
std::vector<int> top_order;
while (!q.empty()) {
int u = q.front();
q.pop();
top_order.push_back(u);
for (int v : adj[u]) {
if (--in_degree[v] == 0) {
q.push(v);
}
}
count++;
}
if (count != V) {
std::cerr << "存在环,无法进行拓扑排序。" << std::endl;
return;
}
for (int i : top_order) {
std::cout << i << " ";
}
std::cout << std::endl;
}
private:
int V;
std::vector<std::vector<int>> adj;
};
int main() {
Graph g(6);
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
std::cout << "拓扑排序结果: ";
g.topologicalSort();
return 0;
}
解释
-
DFS 拓扑排序:
- 通过递归访问每个节点,并在所有相邻节点都访问完后,将当前节点压入栈中。
- 最后从栈中弹出节点就是拓扑排序的顺序。
-
Kahn 算法(BFS 拓扑排序):
- 计算每个节点的入度。
- 将所有入度为0的节点入队。
- 不断从队列中取出入度为0的节点,加入拓扑排序序列,并减少其相邻节点的入度。
- 如果在排序结束时仍存在入度不为0的节点,则说明图中存在环。
适用场景
- 无向图的连通性问题:使用并查集。
- 有向图的依赖性问题:使用DFS或BFS进行拓扑排序。