手把手教你用C++实现离散数学中的图论算法(附完整代码)

张开发
2026/5/10 19:27:13 15 分钟阅读

分享文章

手把手教你用C++实现离散数学中的图论算法(附完整代码)
手把手教你用C实现离散数学中的图论算法附完整代码在计算机科学和离散数学中图论算法是解决网络、路径和关系问题的核心工具。本文将带你从零开始用C实现几个关键的图论算法可简单图化判断、连通图检测和欧拉图判定。不同于教科书式的理论讲解我们将重点关注实际编码中的技巧、常见陷阱和优化方法。无论你是准备算法竞赛的学生还是需要处理图论问题的开发者这些实现都将成为你工具箱中的利器。我们将使用邻接矩阵作为图的表示方法并采用递归和深度优先搜索等经典技术来解决这些问题。1. 图论基础与算法设计思路在开始编码之前我们需要明确几个关键概念和算法设计思路。图论中的许多问题都围绕着顶点和边的性质展开而我们的算法将基于这些性质进行判断和计算。1.1 可简单图化的判定条件一个非负整数序列是否可以作为某个简单图的度数序列需要满足以下条件握手定理所有顶点的度数之和必须是偶数因为每条边会被两个顶点计算Havel-Hakimi定理可以通过递归删除最大度数顶点并调整剩余度数来验证bool isGraphicSequence(const vectorint degrees) { int sum accumulate(degrees.begin(), degrees.end(), 0); if (sum % 2 ! 0) return false; // 不满足握手定理 vectorint temp degrees; while (true) { sort(temp.rbegin(), temp.rend()); // 降序排序 if (temp[0] 0) return true; // 所有度数为0可简单图化 int d temp[0]; if (d temp.size() - 1) return false; // 度数超过可能的最大值 temp.erase(temp.begin()); for (int i 0; i d; i) { if (temp[i] 0) return false; temp[i]--; } } }1.2 连通图与欧拉图的判定连通图从任一顶点出发通过边可以到达所有其他顶点欧拉图包含经过每条边恰好一次的回路所有顶点度数为偶数提示在实际编码中我们通常使用深度优先搜索(DFS)来检测连通性和寻找欧拉回路。2. 图的表示与核心数据结构我们选择邻接矩阵作为图的表示方法虽然对于稀疏图来说这可能不是最高效的但它实现简单且易于理解。2.1 邻接矩阵实现class Graph { private: vectorvectorint adjMatrix; vectorint degrees; int vertexCount; public: Graph(const vectorint degSeq) : degrees(degSeq), vertexCount(degSeq.size()) { adjMatrix.resize(vertexCount, vectorint(vertexCount, 0)); } void buildFromHavelHakimi() { vectorpairint, int nodes; // 存储(度数, 顶点索引) for (int i 0; i vertexCount; i) { nodes.emplace_back(degrees[i], i); } while (true) { sort(nodes.begin(), nodes.end(), greaterpairint, int()); if (nodes[0].first 0) break; int d nodes[0].first; nodes.erase(nodes.begin()); for (int i 0; i d; i) { if (nodes[i].first 0) { throw runtime_error(不可简单图化); } nodes[i].first--; adjMatrix[nodes[0].second][nodes[i].second] 1; adjMatrix[nodes[i].second][nodes[0].second] 1; } } } const vectorvectorint getAdjMatrix() const { return adjMatrix; } };2.2 算法实现中的关键点度数序列处理需要保持度数序列的降序排列邻接矩阵构建避免自环和平行边递归终止条件当所有度数降为0时停止3. 连通性检测实现连通性检测是图论算法中的基础操作我们使用深度优先搜索来实现bool Graph::isConnected() const { if (vertexCount 0) return true; vectorbool visited(vertexCount, false); stackint s; s.push(0); visited[0] true; int count 1; while (!s.empty()) { int v s.top(); s.pop(); for (int i 0; i vertexCount; i) { if (adjMatrix[v][i] !visited[i]) { visited[i] true; count; s.push(i); } } } return count vertexCount; }注意对于大型图递归实现的DFS可能会导致栈溢出此时可以使用迭代实现或广度优先搜索(BFS)。4. 欧拉图判定与回路查找欧拉图的判定相对简单但查找欧拉回路则需要更精细的算法设计。4.1 欧拉图判定bool Graph::isEulerian() const { if (!isConnected()) return false; for (int degree : degrees) { if (degree % 2 ! 0) return false; } return true; }4.2 Hierholzer算法实现欧拉回路vectorint Graph::findEulerCircuit() const { if (!isEulerian()) return {}; vectorvectorint tempAdj adjMatrix; stackint path; vectorint circuit; path.push(0); int current 0; while (!path.empty()) { if (any_of(tempAdj[current].begin(), tempAdj[current].end(), [](int val) { return val 0; })) { path.push(current); for (int i 0; i vertexCount; i) { if (tempAdj[current][i] 0) { tempAdj[current][i]--; tempAdj[i][current]--; current i; break; } } } else { circuit.push_back(current); current path.top(); path.pop(); } } reverse(circuit.begin(), circuit.end()); return circuit; }4.3 算法优化技巧边标记法使用矩阵记录已访问的边避免重复处理快速邻接查询对于大型图可以维护邻接表提高查询效率路径压缩在查找过程中及时合并简单路径5. 完整实现与测试案例将所有组件整合成一个完整的解决方案并提供测试接口#include iostream #include vector #include algorithm #include stack #include numeric #include stdexcept using namespace std; class Graph { // 如前所述的类定义 // ... }; int main() { cout 输入顶点数和度数序列例如5 4 2 2 2 2 endl; int n; cin n; vectorint degrees(n); for (int i 0; i n; i) { cin degrees[i]; } try { Graph graph(degrees); if (!graph.isGraphicSequence()) { cout 度数序列不可简单图化 endl; return 0; } graph.buildFromHavelHakimi(); cout 邻接矩阵 endl; auto adj graph.getAdjMatrix(); for (const auto row : adj) { for (int val : row) { cout val ; } cout endl; } if (graph.isConnected()) { cout 图是连通的 endl; if (graph.isEulerian()) { cout 图是欧拉图 endl; auto circuit graph.findEulerCircuit(); cout 欧拉回路; for (size_t i 0; i circuit.size(); i) { if (i ! 0) cout -; cout v circuit[i] 1; } cout endl; } else { cout 图不是欧拉图 endl; } } else { cout 图不连通 endl; } } catch (const exception e) { cerr 错误: e.what() endl; } return 0; }6. 常见问题与调试技巧在实际实现这些算法时开发者常会遇到一些典型问题Havel-Hakimi算法实现错误忘记在每次迭代后重新排序度数序列没有正确处理度数减为负数的情况欧拉回路查找失败边标记不正确导致无限循环没有正确处理孤立顶点性能问题对于大型图邻接矩阵表示法会消耗过多内存递归实现可能导致栈溢出调试建议从小型测试案例开始如4-5个顶点可视化中间结果打印邻接矩阵使用断言检查不变量如度数之和应为偶数// 示例调试代码片段 void debugPrint(const vectorvectorint matrix) { cout 当前邻接矩阵 endl; for (const auto row : matrix) { for (int val : row) { cout val ; } cout endl; } }7. 实际应用与扩展这些基础图论算法在许多领域都有广泛应用网络分析检测网络连通性寻找最优路径电路设计欧拉回路用于PCB布线社交网络分析用户关系图的连通组件算法扩展方向添加对加权图的支持实现更高效的邻接表表示添加并行计算支持开发可视化组件// 加权图的邻接矩阵示例 class WeightedGraph { private: vectorvectordouble adjMatrix; // 使用double存储权重 public: // 其他实现类似 };在实现这些算法时我经常遇到的一个问题是Havel-Hakimi算法中的顶点索引管理。最初我尝试保持原始顶点顺序但发现这在递归过程中会导致混乱。最终解决方案是在每一步都重新索引顶点只在最后构建邻接矩阵时恢复原始顺序。这个经验告诉我有时候临时转换问题表示形式可以大大简化算法实现。

更多文章