别再暴力搜索了!用C++手写分支限界法,5分钟搞定TSP旅行商问题
用C手写分支限界法5个城市的TSP问题实战指南旅行商问题TSP是计算机科学中最经典的组合优化难题之一也是算法竞赛和面试中的常客。面对5个城市的TSP实例如何高效实现分支限界法本文将带你从零开始用C一步步构建完整解法避开常见陷阱掌握核心优化技巧。1. 分支限界法解决TSP的核心思路分支限界法结合了广度优先搜索和剪枝策略的优势通过智能地扩展最有希望的节点来寻找最优解。对于TSP问题我们需要矩阵规约通过行规约和列规约得到每个节点的下界bound优先队列总是扩展当前界限最小的节点边选择策略精心选择要固定或不固定的边平衡搜索树的宽度和深度关键点矩阵规约后的0值边是我们的主要关注对象选择这些边能保证左子树的界限不会增加2. 数据结构设计与实现2.1 节点表示每个搜索节点需要包含以下信息struct Node { vectorvectorint cost; // 当前耗费矩阵 int bound; // 当前下界 int cityNum; // 剩余城市数量 vectorint path; // path[i]表示城市i的前驱城市 vectorint inpath; // inpath[i]表示城市i的后继城市 Node(vectorvectorint c, int b, int ci) : cost(c), bound(b), cityNum(ci) { path vectorint(ci 1, -1); inpath vectorint(ci 1, -1); } };2.2 优先队列管理使用C的algorithm中的堆操作函数vectorNode searchHeap; // 存储搜索节点 // 自定义比较函数 bool nodeGreater(Node x, Node y) { return x.bound y.bound; } // 堆操作示例 make_heap(searchHeap.begin(), searchHeap.end(), nodeGreater); pop_heap(searchHeap.begin(), searchHeap.end(), nodeGreater); searchHeap.pop_back();3. 关键算法步骤详解3.1 矩阵规约化实现矩阵规约分为行规约和列规约两步void convention(Node eNode) { // 行规约 bool* zeroLocation new bool[eNode.cityNum1]{false}; for (int i 1; i eNode.cityNum; i) { int minum *min_element(eNode.cost[i].begin()1, eNode.cost[i].end()); eNode.bound minum; for (int j 1; j eNode.cityNum; j) { if (eNode.cost[i][j] ! INT_MAX) { eNode.cost[i][j] - minum; if (eNode.cost[i][j] 0) zeroLocation[j] true; } } } // 列规约 for (int j 1; j eNode.cityNum; j) { if (!zeroLocation[j]) { int minum INT_MAX; for (int i 1; i eNode.cityNum; i) if (eNode.cost[i][j] minum) minum eNode.cost[i][j]; eNode.bound minum; for (int i 1; i eNode.cityNum; i) if (eNode.cost[i][j] ! INT_MAX) eNode.cost[i][j] - minum; } } delete[] zeroLocation; }3.2 最优边选择策略选择能使右子树界限最大的0值边int maxminSide 0; int maxIndex[2] {1, 1}; for (int i 1; i eNode.cityNum; i) { int minum INT_MAX; int zeroCount 0; int zeroCol 1; for (int j 1; j eNode.cityNum; j) { if (eNode.cost[i][j] 0) { if (zeroCount 1) break; zeroCol j; } else if (eNode.cost[i][j] minum) { minum eNode.cost[i][j]; } } if (zeroCount 1 minum maxminSide) { maxminSide minum; maxIndex[0] i; maxIndex[1] zeroCol; } }4. 左右子树生成与优化4.1 右子树生成不选边vectorvectorint rCost eNode.cost; rCost[maxIndex[0]][maxIndex[1]] INT_MAX; // 对选中行重新规约 int rowMin *min_element(rCost[maxIndex[0]].begin()1, rCost[maxIndex[0]].end()); for (int j 1; j eNode.cityNum; j) if (rCost[maxIndex[0]][j] ! INT_MAX) rCost[maxIndex[0]][j] - rowMin; Node rNode(rCost, eNode.bound rowMin, eNode.cityNum, eNode.path, eNode.inpath);4.2 左子树生成选边// 获取实际城市编号 int start eNode.cost[maxIndex[0]][0]; int dest eNode.cost[0][maxIndex[1]]; // 设置反向边为∞ for (int i 1; i eNode.cityNum; i) { if (eNode.cost[i][0] dest eNode.cost[0][i] start) { eNode.cost[i][i] INT_MAX; break; } } // 更新路径 vectorint lpath eNode.path; vectorint linpath eNode.inpath; lpath[dest] start; linpath[start] dest; // 防止子回路 if (eNode.cityNum 1) { int u start, v dest; while (lpath[u] ! -1) u lpath[u]; while (linpath[v] ! -1) v linpath[v]; // 设置u→v为∞ // ... } // 创建缩减后的矩阵 vectorvectorint lCost; // ... 省略矩阵缩减代码 ... Node lNode(lCost, eNode.bound, eNode.cityNum-1, lpath, linpath); convention(lNode);5. 完整算法流程与性能优化5.1 主算法框架void TSP(int num) { Node initial(initialCost, 0, num); convention(initial); searchHeap.push_back(initial); make_heap(searchHeap.begin(), searchHeap.end(), nodeGreater); while (!searchHeap.empty()) { Node eNode searchHeap.front(); pop_heap(searchHeap.begin(), searchHeap.end(), nodeGreater); searchHeap.pop_back(); if (eNode.cityNum 0) { printSolution(eNode); return; } // 边选择、左右子树生成 // ... // 新节点加入堆 searchHeap.push_back(rNode); searchHeap.push_back(lNode); push_heap(searchHeap.begin(), searchHeap.end(), nodeGreater); } }5.2 关键优化技巧提前终止当堆顶节点的bound大于当前最优解时可以提前终止矩阵存储优化使用一维数组存储对称矩阵减少内存占用并行规约对行列规约进行并行化处理缓存友好优化数据访问模式提高缓存命中率6. 实战中的常见问题与解决方案6.1 INT_MAX溢出处理// 在计算bound时检查溢出 if (rbound 0) rbound INT_MAX;6.2 路径记录优化使用两个数组分别记录前驱和后继避免复杂的路径重建vectorint path(n1, -1); // path[i] j 表示j→i vectorint inpath(n1, -1); // inpath[i] j 表示i→j6.3 大规模数据应对对于超过15个城市的问题分支限界法可能不再适用可以考虑启发式算法如遗传算法、模拟退火动态规划状态压缩适用于20个城市左右近似算法如Christofides算法在实际项目中遇到TSP问题时分支限界法的最佳适用场景是城市数量在5-12个之间的精确求解需求。我曾在一个物流调度项目中使用这种实现相比暴力搜索将求解时间从小时级降低到秒级。