用C++手搓一个旅行商问题求解器:从矩阵规约到最小堆优化的完整实现
用C手搓一个旅行商问题求解器从矩阵规约到最小堆优化的完整实现旅行商问题TSP是计算机科学中最经典的组合优化难题之一它要求找到一条访问所有城市并返回起点的最短路径。对于C开发者而言实现一个高效的TSP求解器不仅能深入理解算法本质还能锻炼工程实践能力。本文将带你从零开始构建一个基于分支限界法的最小堆优化求解器涵盖矩阵规约、状态空间搜索和性能优化等关键技术点。1. 核心算法设计分支限界法的实现逻辑分支限界法的核心思想是通过系统性地枚举问题的解空间同时利用界限函数剪枝无效分支。在TSP问题中我们需要解决三个关键问题如何表示搜索节点每个节点需要包含当前路径、剩余城市和耗费矩阵的状态如何计算界限值通过矩阵规约化技术得到当前状态的最小可能耗费如何组织搜索树使用最小堆优先扩展最有希望的节点1.1 节点数据结构设计我们使用结构体Node封装搜索节点关键字段包括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. 矩阵规约化降低问题规模的利器矩阵规约是分支限界法的核心操作它通过行列变换使每行每列至少出现一个0同时累计规约值作为界限的基础。2.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) minum min(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; }提示规约化后的矩阵中0元素表示潜在的优选边这为后续的边选择策略奠定了基础。3. 最小堆优化加速最优解发现传统队列实现会盲目扩展所有节点而最小堆总优先扩展界限最小的节点大幅提升搜索效率。3.1 堆操作实现// 自定义堆比较函数 bool nodeGreater(Node x, Node y) { return x.bound y.bound; } // 堆操作示例 vectorNode searchHeap; // 插入节点 searchHeap.push_back(newNode); push_heap(searchHeap.begin(), searchHeap.end(), nodeGreater); // 提取最小节点 Node eNode searchHeap.front(); pop_heap(searchHeap.begin(), searchHeap.end(), nodeGreater); searchHeap.pop_back();这种实现的时间复杂度为O(log n)相比普通队列可以提前发现优质解。4. 边选择策略与子树生成边选择直接影响算法效率我们采用最大右子树界限策略迫使算法优先探索更优的左子树。4.1 最优边选择算法int maxminSide 0; int maxIndex[2] {1,1}; for (int i 1; i eNode.cityNum; i) { int minum INT_MAX; int zeroCount 0; int zeroPos 1; for (int j 1; j eNode.cityNum; j) { if (eNode.cost[i][j] 0) { if (zeroCount 1) break; zeroPos 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] zeroPos; } }4.2 左右子树生成右子树不选边只需将对应边置为∞并重新规约vectorvectorint rCost eNode.cost; rCost[maxIndex[0]][maxIndex[1]] INT_MAX; Node rNode(rCost, eNode.bound maxminSide, eNode.cityNum, eNode.path, eNode.inpath); convention(rNode);左子树选边需要处理路径更新和矩阵缩减// 获取实际城市编号 int start eNode.cost[maxIndex[0]][0]; int dest eNode.cost[0][maxIndex[1]]; // 更新路径 vectorint lpath eNode.path; lpath[dest] start; vectorint linpath eNode.inpath; 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]; // 将(v,u)置为∞ ... } // 缩减矩阵 vectorvectorint lCost; for (int i 0; i eNode.cityNum 1; i) { if (i ! maxIndex[0]) { vectorint row; for (int j 0; j eNode.cityNum 1; j) { if (j ! maxIndex[1]) row.push_back(eNode.cost[i][j]); } lCost.push_back(row); } } Node lNode(lCost, eNode.bound, eNode.cityNum-1, lpath, linpath); convention(lNode);5. 性能优化技巧与实践建议在实际实现中以下几个优化点可以显著提升性能矩阵存储优化使用一维数组模拟二维矩阵减少内存分配开销路径压缩当剩余城市较少时可以改用更紧凑的表示方法提前终止当堆顶节点的界限已大于当前最优解时可立即终止搜索// 示例内存优化版矩阵拷贝 vectorvectorint fastMatrixCopy(const vectorvectorint src) { vectorvectorint dst; dst.reserve(src.size()); for (const auto row : src) { dst.emplace_back(row.begin(), row.end()); } return dst; }经过完整实现后对于5个城市的TSP问题算法通常能在毫秒级找到最优解。更大的城市规模则需要考虑启发式算法或近似解法这正是分支限界法的局限性所在——它虽然能保证找到最优解但时间复杂度随城市数量呈指数级增长。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2434623.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!