旅行售货员问题(分支限界法)
一、实验内容
运用分支限界法解决0-1背包问题(或者旅行售货员问题、或者装载问题、或者批处理作业调度)
使用优先队列式分支限界法来求解旅行售货员问题
二、所用算法基本思想及复杂度分析
1.算法基本思想
分支限界法常以广度优先或以最小耗费有限的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。在搜索问题的解空间树时,分支限界法和回溯法的主要区别在于它们对当前扩展节点所采用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展节点。活结点一旦成为扩展节点,就一次性产生其所有儿子节点。在这些儿子节点中,导致不可行解或导致非最优解的儿子节点被舍弃,其余儿子节点被加入活结点表中。此后,从活结点表中取下一节点为当前扩展节点。并重复上述节点扩展过程。这个过程移至持续到找到所需的解或活结点表为空为止。
从活结点表中选择下一扩展节点的不同方式导致不同的分支限界法。
最常见的有以下两种方式:
(1)队列式分支限界法
队列式分支限界法将活结点表组织成一个队列,并按队列的先进先出原则选取下一个节点为当前扩展节点。
(2)优先队列式分支限界法
优先队列式的分支限界法将活结点表组织成一个优先队列,并按优先队列中规定的节点优先级选取优先级最高的下一个节点成为当前扩展节点。
2.问题分析及算法设计
问题分析:
(1)解旅行售货员问题的优先队列式分支限界法用优先队列存储活结点表。
(2)活结点m在优先队列中的优先级定义为:活结点m对应的子树费用下界lcost。
(3)lcost=cc+rcost,其中,cc为当前结点费用,rcost为当前顶点最小出边费用加上剩余所有顶点的最小出边费用和。
(4)优先队列中优先级最大的活结点成为下一个扩展结点。
(5)排列树中叶结点所相应的载重量与其优先级(下界值)相同,即:叶结点所相应的回路的费用(bestc)等于子树费用下界lcost的值。
算法设计:
(1) 要找最小费用旅行售货员回路,选用最小堆表示活结点优先队列。
(2) 算法开始时创建一个最小堆,用于表示活结点优先队列。
(3) 堆中每个结点的优先级是子树费用的下界lcost值。
(4) 计算每个顶点i的最小出边费。
(5) 如果所给的有向图中某个顶点没有出边,则该图不可能有回路,算法结束。
特例:(如下图所示)
- 算法复杂度分析
对于分支限界法的复杂度是根据数据的不同而不同,搜索的节点越少,复杂度越低,这跟目标的选取有很大的关系,目标的函数值计算也是需要一定的时间。
三、源程序核心代码及注释(截图)
四、运行结果
五、调试和运行程序过程中产生的问题及解决方法,实验总结(5行以上)
对于这一次实验,感觉分支限界法调试起来,起初递归来回跳,看的头昏眼花的,看的云里雾里的,后面自己去画了个草图,结合图来,一步一步的调试,这里还需要判断是否符合最优解,当在父节点的最优总权值>最优总权值时,这时就要直接剪枝,这里主要是限界条件的当前结点的走过的路径长度<最短路径长度,这里的最短路径长度并不一定是最优解,而只是计算到某一步得到的最优解。这让调试看起来,来回跳动,最终将其它子节点按优先级顺序依次存入队列,然后挑出队列中优先级最高的节点作为下次扩展节点,然后一直重复上述的操作,最后调试完毕,就可以得到最优解。
源码:
#include<bits/stdc++.h>
#define NO -1 //没有通路
#define MAX_WEIGHT 4000
using namespace std;
int n; //城市数目
int City_Graph[100][100]; //保存图信息
int x[100]; //x[i]保存第i步遍历的城市
int isIn[100]; //保存 城市i是否已经加入路径
int bestw; //最优路径总权值
int cw; //当前路径总权值
int bestx[100]; //最优路径
void Travel_Backtrack(int t)
{
int i, j;
if (t > n) { //到达叶子节点,走完了,输出结果
if (cw < bestw) { //判断当前路径是否是更优解
for (i = 1; i <= n; i++)
{
bestx[i] = x[i];//保存最优路劲
}
bestw = cw + City_Graph[x[n]][1];//最优路劲总权值
}
return;
}
else {
for (j = 1; j <= n; j++) { //找到第t步能走的城市
if (City_Graph[x[t - 1]][j] != NO && !isIn[j]) { //能到而且没有加入到路径中
isIn[j] = 1;//加入路劲
x[t] = j;//保存所在城市
cw += City_Graph[x[t - 1]][j];//加上当前路劲总权值
Travel_Backtrack(t + 1);//递归回溯
isIn[j] = 0;//还原状态
x[t] = 0;//还原状态
cw -= City_Graph[x[t - 1]][j];//还原当前路劲总权值
}
}
}
}
int main() {
int i;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> City_Graph[i][j];
}
}
//测试递归法
for (i = 1; i <= n; i++) {
x[i] = 0; //表示第i步还没有解
bestx[i] = 0; //还没有最优解
isIn[i] = 0; //表示第i个城市还没有加入到路径中
}
x[1] = 1; //第一步 走城市1
isIn[1] = 1; //第一个城市 加入路径
bestw = MAX_WEIGHT;
cw = 0;
Travel_Backtrack(2); //从第二步开始选择城市
cout << bestw;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)