高级图论
高级图论算法包括二分图匹配、欧拉回路、强连通分量和双连通分量。这些算法是解决竞赛编程中复杂图论问题的关键。
二分图与匹配
二分图可以分为两个不相交的集合,边只连接来自不同集合的顶点。
二分图检测和最大匹配
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class BipartiteChecker {
private:
vector<vector<int>> graph;
vector<int> color;
int n;
public:
BipartiteChecker(int vertices) : n(vertices) {
graph.resize(n);
color.assign(n, -1);
}
void addEdge(int u, int v) {
graph[u].push_back(v);
graph[v].push_back(u);
}
bool isBipartite() {
for (int start = 0; start < n; start++) {
if (color[start] == -1) {
queue<int> q;
q.push(start);
color[start] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : graph[u]) {
if (color[v] == -1) {
color[v] = 1 - color[u];
q.push(v);
} else if (color[v] == color[u]) {
return false;
}
}
}
}
}
return true;
}
};
int main() {
BipartiteChecker checker(6);
checker.addEdge(0, 3);
checker.addEdge(0, 4);
checker.addEdge(1, 3);
checker.addEdge(1, 5);
checker.addEdge(2, 4);
checker.addEdge(2, 5);
if (checker.isBipartite()) {
cout << "图是二分图!" << endl;
} else {
cout << "图不是二分图!" << endl;
}
return 0;
}使用BFS染色法检查顶点是否可以分为两个集合,集合内部没有边。
强连通分量 (SCC)
强连通分量是一个最大顶点集合,使得分量中每个顶点都能到达分量中的其他所有顶点。
Tarjan算法求强连通分量
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;
class TarjanSCC {
private:
vector<vector<int>> graph;
vector<int> dfn, low, sccId;
vector<bool> inStack;
stack<int> st;
int timer, sccCount;
void tarjan(int u) {
dfn[u] = low[u] = ++timer;
st.push(u);
inStack[u] = true;
for (int v : graph[u]) {
if (dfn[v] == 0) {
// 未访问的顶点
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (inStack[v]) {
// 指向当前SCC中顶点的后向边
low[u] = min(low[u], dfn[v]);
}
}
// 如果u是SCC的根
if (dfn[u] == low[u]) {
int v;
do {
v = st.top();
st.pop();
inStack[v] = false;
sccId[v] = sccCount;
} while (v != u);
sccCount++;
}
}
public:
TarjanSCC(int n) : graph(n), dfn(n, 0), low(n, 0),
sccId(n, -1), inStack(n, false),
timer(0), sccCount(0) {}
void addEdge(int u, int v) {
graph[u].push_back(v);
}
void findSCC() {
for (int i = 0; i < graph.size(); i++) {
if (dfn[i] == 0) {
tarjan(i);
}
}
}
int getSCCCount() { return sccCount; }
vector<int> getSCCIds() { return sccId; }
// 构建缩点图
vector<vector<int>> buildCondensationGraph() {
vector<vector<int>> condensed(sccCount);
vector<set<int>> edges(sccCount);
for (int u = 0; u < graph.size(); u++) {
for (int v : graph[u]) {
if (sccId[u] != sccId[v]) {
edges[sccId[u]].insert(sccId[v]);
}
}
}
for (int i = 0; i < sccCount; i++) {
for (int v : edges[i]) {
condensed[i].push_back(v);
}
}
return condensed;
}
};
int main() {
cout << "Tarjan强连通分量算法测试:" << endl;
TarjanSCC tarjan(8);
// 构建包含强连通分量的有向图
tarjan.addEdge(0, 1);
tarjan.addEdge(1, 2);
tarjan.addEdge(2, 0); // SCC: {0, 1, 2}
tarjan.addEdge(2, 3);
tarjan.addEdge(3, 4);
tarjan.addEdge(4, 5);
tarjan.addEdge(5, 3); // SCC: {3, 4, 5}
tarjan.addEdge(5, 6);
tarjan.addEdge(6, 7); // SCC: {6}, {7}
tarjan.findSCC();
cout << "强连通分量数量: " << tarjan.getSCCCount() << endl;
vector<int> sccIds = tarjan.getSCCIds();
cout << "SCC分配:" << endl;
for (int i = 0; i < sccIds.size(); i++) {
cout << "顶点 " << i << " -> SCC " << sccIds[i] << endl;
}
auto condensed = tarjan.buildCondensationGraph();
cout << "\n缩点图边:" << endl;
for (int i = 0; i < condensed.size(); i++) {
cout << "SCC " << i << " -> ";
for (int v : condensed[i]) {
cout << "SCC " << v << " ";
}
cout << endl;
}
return 0;
}Tarjan算法使用带时间戳的DFS在O(V+E)时间内找到强连通分量。它维护一个顶点栈,使用low-link值来识别SCC的根。
欧拉图与欧拉回路
欧拉回路是恰好经过每条边一次的闭合路径。当且仅当每个顶点的度数都是偶数时,图才有欧拉回路。
Hierholzer算法求欧拉回路
#include <iostream>
#include <vector>
#include <stack>
#include <list>
using namespace std;
class EulerianCircuit {
private:
vector<list<int>> graph;
int n;
public:
EulerianCircuit(int vertices) : n(vertices), graph(vertices) {}
void addEdge(int u, int v) {
graph[u].push_back(v);
graph[v].push_back(u);
}
bool hasEulerianCircuit() {
// 检查所有顶点的度数是否为偶数
for (int i = 0; i < n; i++) {
if (graph[i].size() % 2 != 0) {
return false;
}
}
return true;
}
vector<int> findEulerianCircuit() {
if (!hasEulerianCircuit()) {
return {};
}
vector<int> circuit;
stack<int> currPath;
// 从顶点0开始
currPath.push(0);
while (!currPath.empty()) {
int curr = currPath.top();
if (!graph[curr].empty()) {
// 取下一条边
int next = graph[curr].front();
graph[curr].pop_front();
// 删除反向边
graph[next].remove(curr);
currPath.push(next);
} else {
// 没有更多边,添加到回路
circuit.push_back(curr);
currPath.pop();
}
}
return circuit;
}
void printCircuit(const vector<int>& circuit) {
if (circuit.empty()) {
cout << "不存在欧拉回路!" << endl;
return;
}
cout << "欧拉回路: ";
for (int i = circuit.size() - 1; i >= 0; i--) {
cout << circuit[i];
if (i > 0) cout << " -> ";
}
cout << endl;
}
};
int main() {
cout << "Hierholzer欧拉回路算法测试:" << endl;
// 创建有欧拉回路的图
EulerianCircuit ec(5);
// 构建环图: 0-1-2-3-4-0 并添加额外边
ec.addEdge(0, 1);
ec.addEdge(1, 2);
ec.addEdge(2, 3);
ec.addEdge(3, 4);
ec.addEdge(4, 0);
ec.addEdge(0, 2); // 额外边使度数为偶数
if (ec.hasEulerianCircuit()) {
cout << "图有欧拉回路!" << endl;
vector<int> circuit = ec.findEulerianCircuit();
ec.printCircuit(circuit);
} else {
cout << "图没有欧拉回路!" << endl;
}
return 0;
}Hierholzer算法通过构建路径和合并环来找到欧拉回路。它沿着边前进直到没有更多边可用,然后回溯寻找额外的环。