目录
一、实验目的
二、实验环境
三、实验内容
四、核心代码
五、记录与处理
六、思考与总结
七、完整报告和成果文件提取链接
一、实验目的
针对复杂装载问题、及0/1背包问题开展分析、建模、评价,算法设计与优化,并进行编码实践。
理解复杂装载问题及0/1背包问题的回溯法求解策略,能够对比分析与动态规划求解复杂装载问题的策略差异;能够根据具体的问题进行解空间树的构造及剪枝函数设计;针对如上两个问题利用回溯法进行设计求解,并进行对比两个问题求解时的异同。并利用JAVA/C/C++等编程语言将算法转换为对应的程序上机运行(语言自选)。
二、实验环境
1、机房电脑 Window10
2、Eclipse /DEVC++等
三、实验内容
实验要求:
1、掌握回溯法进行问题分析及建模的过程;
2、复杂装载、及0/1背包问题能够利用回溯法开展算法设计与优化;
(1)复杂装载问题背景:指定两艘船容量C1,C2,给定n个货物,各物品重量wi已知。请问是否可以成功装入两艘船,及如何选择物品分别装入两艘船?
(2)0/1背包问题背景:指定背包容量C,给定n个商品,各商品重量wi、价值vi已知。如何选择商品,使得装入背包的价值最大?
3、能够对上述问题的回溯法进行时间复杂性分析,并与动态规划进行对比。
【回溯法原理】
回溯法是一种暴力搜索方法,它将问题的解表示为n元组(x1, x2, …, xn)的形式。其核心在于逐步构建一个解空间,过程中涉及选择、判断及必要的回退步骤,直至找到问题的解或确认无解。这种求解思路类似于逐步探索的过程,一旦当前路径不满足求解条件,便“回溯”以尝试其他路径。
解决一个问题的所有可能的决策序列就是解空间。
对于回溯法解决的问题而言,它的解空间一般用树或图的形式来组织,即为解空间树,树的每一个结点确定每个问题的一个状态。树的根结点代表初始状态,以下每一层代表所作出的选择。
由于回溯法解决的问题较为冗杂,情况复杂,所以用回溯法搜索解空间时,通常采用两种策略避免无效搜索,提高回溯的搜索效率。
实验原理:
1、针对复杂装载背包问题,如何利用回溯法进行算法设计
【装载方案】
(1)首先将第一艘轮船尽可能装满;
(2)然后将剩余的集装箱装在第二艘轮船上;
将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,在承载范围内,使该子集中集装箱重量之和最大。
复杂装载问题的解空间是一个子集树。
【求解思路】
(1) 将尽可能多的集装箱装到第一艘船上
(2) 约束条件判断:cw + w[i] <= c,判断当前路径是否可以装载当前集装箱 i 而不超过船的最大承载能力 c
(3) 剪枝函数(限界条件):cw + r >bestw,判断当前路径是否有可能超过已知的最佳装载重量 bestw。
(4) 终止条件判断:当i>n时,说明已经遍历到叶子节点,此时判断cw > bestw,这个条件的作用是判断当前路径的装载重量 cw 是否超过了已知的最佳装载重量 bestw,如果满足则需要更新 bestw 为当前的装载重量 cw。
利用回溯法求解时:在最坏情况下,每个集装箱都有两个选择(装或不装),因此递归树的分支因子为 2。因此,递归树的最大深度为 n,每层最多有两个分支。最坏情况时间复杂度是O( ),剪枝策略的存在,会使实际运行时间通常会低于这个值,回溯法的空间复杂度为 O(n)
采用动态规划算法时,时间复杂度为动态规划算法O(nc) ,最优空间复杂度为O(c)
一般情况下使用动态规划算法解决复杂装载问题的时间复杂度更优。
2、针对0/1背包问题,如何利用回溯法进行算法设计
【装载方案】
背包容量限制:每次选择物品时,必须确保当前背包中的物品总重量不超过背包容量 c。
物品选择限制:每个物品只能选择一次(0/1背包问题)。
最优解要求:在所有满足约束条件的解中,选择总价值最大的解。
【求解思路】
(1) 将尽可能多的价值高的装到背包中
(2) 约束条件判断:cw + w[i] <= c,该条件用于判断当前物品是否可以被选择。
(3) 剪枝函数(限界条件):剪枝函数 bound 用于计算当前解的上界,以决定是否继续搜索某个分支。cp+rp>bestp
(4) 终止条件判断:条件:i > n,当所有物品都已经考虑完毕时, i 超过物品数量 n时,检查当前解的价值 cp 是否大于已知的最佳价值 bestp。如果当前解更好,则更新最佳价值 bestp 并记录当前解 bestx。
对于0/1背包问题,回溯法方法求解时,计算上界需要O(n)时间,在最坏情况下有O( )个右儿子结点需要计算上界,所以时间复杂度为O( ),回溯法求解的空间复杂度为O(n)。
采用动态规划算法时,时间复杂度为O(nc) ,最优空间复杂度为O(c)
一般情况下使用动态规划算法解决0/1背包问题的时间复杂度更优。
3、针对同一问题,对比动态规划及回溯法求解的算法复杂度。
1.利用回溯法求解时:在最坏情况下,每个集装箱都有两个选择(装或不装),因此递归树的分支因子为 2。因此,递归树的最大深度为 n,每层最多有两个分支。最坏情况时间复杂度是O(2n ),剪枝函数会使实际运行时间通常会低于这个值,回溯法求解的空间复杂度为O(n)。
采用动态规划算法时,时间复杂度为O(nc) , 最优空间复杂度为O(c)
一般情况下使用动态规划算法解决复杂装载问题的时间复杂度更优。
对于0/1背包问题,回溯法方法求解时,计算上界需要O(n)时间,在最坏情况下有O(2n )个右儿子结点需要计算上界,所以时间复杂度为O(n2n ),回溯法求解的空间复杂度为O(n)。
采用动态规划算法时,时间复杂度为O(nc) ,最优空间复杂度为O(c)
一般情况下使用动态规划算法解决0/1背包问题的时间复杂度更优。
四、核心代码
// 回溯函数,用于搜索所有可能的解
void backtrack(int i) {if (i > n) { // 如果已经考虑完所有物品if (cp > bestp) { // 如果当前价值大于已知最佳价值bestp = cp; // 更新最佳价值for (int j = 1; j <= n; j++) {bestx[j] = x[j]; // 记录最佳解}}return;}// 搜索左子树:选择当前物品if (cw + w[i] <= c) {x[i] = 1; // 标记选择当前物品cw += w[i]; // 更新当前重量cp += p[i]; // 更新当前价值backtrack(i + 1); // 递归处理下一个物品cw -= w[i]; // 回溯,恢复当前重量cp -= p[i]; // 回溯,恢复当前价值}// 搜索右子树:不选择当前物品if (bound(i + 1) > bestp) { // 只有当上界大于已知最佳价值时才继续搜索x[i] = 0; // 标记不选择当前物品backtrack(i + 1); // 递归处理下一个物品}
}
void backtrack(int i) { //搜索深度为i的结点 if (i > n) { // 如果所有货物都考虑完毕if (cw1 + cw2 > bestw) { // 如果当前装载的总重量大于已知的最优解bestw = cw1 + cw2; // 更新最优解的重量for (int j = 0; j <= n; ++j) {bestx[j] = x[j]; // 保存当前的装载方案}}return;}// 尝试将第i个物品放入第一艘船if (cw1 + w[i] <= C1 && cw1 + r1 > bestw) {x[i] = 1;cw1 += w[i]; // 更新第一艘船的装载重量r1 -= w[i]; // 更新剩余重量backtrack(i + 1);cw1 -= w[i]; // 回溯,恢复状态r1 += w[i];}
}
五、记录与处理
1、复杂装载问题实验数据及结果分析。
当输入C1=50,C2=50,w={10,40,40}时,所有货物都装进去了,最优装载量为90
2、0/1背包问题实验数据及结果分析。
六、思考与总结
通过本次实验,我对回溯法有了更深刻的理解。回溯法通过递归的方式,逐步构建问题的解空间,并在搜索过程中使用剪枝函数来避免无效搜索,从而提高搜索效率。这种方法在解决组合优化问题时非常有效,尤其是在解决解空间较小或可以通过剪枝显著减少搜索空间的一类问题时。
七、完整报告和成果文件提取链接
完整可运行代码以及相关实验报告以下链接可获取:
链接: https://pan.baidu.com/s/1iss6-C2nxZQGkEpo-WDn0A?pwd=g5cg 提取码: g5cg