一、实验目的
1.掌握基于分支限界的算法求解最小重量机器设计问题的原理和编写分支限界函数的具体步骤。
2.掌握分支限界法的基本思想并通过求解最小重量机器设计问题体会使用优先队列分支限界的方法,从而理解分支限界算法的基本求解过程。
3.体会分支限界算法求解问题的便利和所编写程序的明确结构和良好的可读性。
4.具备运用分支限界算法的思想设计算法并用于求解其他实际应用问题的能力。
二、实验环境
操作系统:Windows10
文本编辑器:VisualStudio Code
实验语言和编译器:C++
编译器:gcc 8.1.0
实验终端:WindowsPowerShell
三、实验内容
设某一个机器由n个部件组成,每一种部件都可以从m个不同的供应商处购得。设wij是从供应商j处购得的部件i的重量,cij是相应的价格。设计一个优先队列式分支限界法,给出总价格不超过d的最小重量机器设计。
程序输入n,m,d为该机器部件的个数,供应商的个数和给出的总价格。并依次输入零件的价格数组和零件的重量数组。要求程序输出最小重量,并给出采购的零件所来源的供应商。
四、算法描述
对于优先队列式分支限界法,将优先级定义为结点已知部分的总重量,即每一次选择新的扩展结点都是选择已知部分总重量最小的结点,剪枝函数是总价格不超过所要求的价格d,界限函数(当前扩展结点的总质量最小值大于当前最优总重量)可以省略。
为方便代码实现,定义node结构体表示解空间中的结点及其各种属性,优先队列中结点的优先级顺序使用仿函数实现,排序逻辑为按照level降序排序(level为node中属相,代表了当前迭代的层次),排序时如果两个结点的weight(当前已选机器重量和)相同,则选择结点中level大的,否则让结点中weight小的优先入队。
优先队列使用STL中priority_queue实现,仿函数实现自定义排序优先级规则代码逻辑如下:
struct cmp
{
bool operator()(node a, node b) const
{
if (a.weight == b.weight)
return a.level < b.level;
return a.weight > b.weight;
}
};
优先队列分支限界法搜索由函数minWeight实现,函数开始时,首先对优先队列进行初始化,对于结点代表的机器部件价值,若该价值大于d,可以提前剪去该枝叶,对于满足约束条件的结点,执行入队操作。
分支限界法的步骤可概括为,首先将扩展结点的所有儿子结点加至活结点列表中并利用约束函数进行剪枝,其次按照“先进先出”原则,选择下一个扩展结点。函数minWeight的实现可用流程图描述如下(流程图使用processon网站完成制作):
代码逻辑如下:
void minWeight()
{
for(int i = 0; i < m; i++)
{//优先队列初始化
node temp=node(w[0][i], c[0][i], 0, i);
if (temp.cost <= d)
{
temp.value[0] = i + 1;
q.push(temp);
}
}
while(!q.empty())
{
node temp = q.top();
q.pop();
if(temp.level < n-1)
{
for(int i = 0; i < m; i++)
{
node t =node(temp.weight+w[temp.level+1][i],
temp.cost+c[temp.level+1][i],
temp.level+1, i);
if(t.level == n-1)
{//到达叶子节点
if(t.weight < min_weight&& t.cost <= d)
{
min_weight =t.weight;//最小重量更新
for(int i = 0;i < n-1; i++)//最优路径更新
ans[i] =temp.value[i];
ans[n-1] = i + 1;
}
}
if(t.level < n-1)//非叶子节点
{
if(t.cost <= d)
{//剪枝(价格)
for(int i = 0; i <t.level; i++)//历史路径
t.value[i] =temp.value[i];
t.value[t.level] = i +1;//路径更新
q.push(t);}}}}}}
算法时间复杂度分析,分支限界法的时间复杂度取决于限界函数的时间复杂度,为O(mn)。
五、实验结果
第一组程序输入部件和供应商的个数均为3,总价格d为4,价值数组和重量数组如下。
部件价格 | 供应商1 | 供应商2 | 供应商3 |
部件1 | 1 | 2 | 3 |
部件2 | 3 | 2 | 1 |
部件3 | 2 | 2 | 2 |
部件重量 | 供应商1 | 供应商2 | 供应商3 |
部件1 | 1 | 2 | 3 |
部件2 | 3 | 2 | 1 |
部件3 | 2 | 2 | 2 |
通过程序求解可知,组成机器所需部件的最小重量为4,其中部件1来自于供应商1,部件2来自于供应商3,部件3来自于供应商1。
第二组输入来自网上所给样例,机器所需要的部件个数为8种,由18个供应商提供,总价格d为14,部件的价格数组和重量数组如图。
程序输出组成机器所需部件的最小重量为57,分别来自于供应商13,6,7,3,18,14,10,16。
六、实验总结
通过基于分支限界的最小重量机器设计的算法设计与编程求解,再一次加深了我对于分支限界算法理解。分支限界算法首先将扩展结点的所有儿子结点加至活结点列表中并利用约束函数进行剪枝,其次按照“先进先出”原则,选择下一个扩展结点,如此迭代直至求解结束。
同时在分支限界算法编程实现中,一定要注意限界条件和剪枝条件中小于等于号的问题,对于一个剪枝条件若漏写等于号就可能得到与预想输出完全不同的结果,通过编程实现,加强了我对于编程求解问题细节的把控。对于优先队可以使用STL中的priority_queue实现,使用自带的优先队列,每次编程只需根据算法约束的不同编写不同的优先级排序规则,而无需自己重复造轮子,大大缩短了开发程序的时间,使得注意力和精力可以全力投入在算法的实现上。
通过这几次实验,我深刻理解到,对于一个给定的问题不能急于上机实现,而应该仔细分析问题的结构,选择适合的算法,并且在草稿纸上理清算法逻辑,最后再上机实现,只有这样当出现问题时,才能更好的定位错误位置而不至于无从下手。对于同一问题,要多尝试各种不同的解法,从不同方向解决问题不仅可以提高剖析问题的能力,还能增强算法设计的水平,同时要多多阅读优秀的代码实现,不断改良自己的代码风格,以写出更清晰易懂且健壮的程序。