算法第五章实验报告
一、题目描述
设某一机器由n个部件组成,每一种部件都可以从m个不同的供应商处购得。设wij是从供应商j 处购得的部件i的重量,cij是相应的价格。 试设计一个算法,给出总价格不超过d的最小重量机器设计。
输入格式:
第一行有3 个正整数n ,m和d, 0<n<30, 0<m<30, 接下来的2n 行,每行n个数。前n行是c,后n行是w。
输出格式:
输出计算出的最小重量,以及每个部件的供应商
输入样例:
3 3 4
1 2 3
3 2 1
2 2 2
1 2 3
3 2 1
2 2 2
输出样例:
在这里给出相应的输出。例如:
4
1 3 1
二、分析
2.1解空间
(1)解空间是穷举所有可能解的空间
(2)当供应商为3个,部件为2个时,解空间是{(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)}
2.2解空间树
2.3结点的状态值
每个结点记录了从第一个结点到此结点的总价值和总重量;如图中第2层第1个结点是第一层的那个结点和这个结点加起来的总价值和总重量
三、算法
#include <iostream> using namespace std; int w[30][30], v[30][30]; int x[30], p[30]; int n, m, d; int minv, minw = 99999999, sumv, sumw; //回溯法 void backtrace(int t) { if (sumv > d) { return; } if (t > n) { if(minw > sumw) { minw = sumw; for (int i = 1;i <= n;i++) { p[i] = x[i]; } } return; } for (int i = 1;i <= m;i++) { x[t] = i; sumv += v[t][i]; sumw += w[t][i]; if (sumw <= minw) { backtrace(t + 1); } sumv -= v[t][i]; sumw -= w[t][i]; } return; } int main() { cin >> n >> m >> d; for (int i = 1;i <= n;i++) { for (int j = 1;j <= m;j++) { cin >> v[i][j]; } } for (int i = 1;i <= n;i++) { for (int j = 1;j <= m;j++) { cin >> w[i][j]; } } backtrace(1); cout << minw << endl; for (int i = 1;i <= n;i++) { cout << p[i] << " "; } }
四、总结
1、回溯法顾名思义,每次算完当前的可能回溯到当初的状态计算另一种可能;回溯法首先根据题目决定是解空间树是子集树还是排列树;两种空间树的算法不同,有些题两种都可以,有些题只能其中一种;
2、子集树:当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树成为子集树;树的每一层两种走向,一种是选择当前的东西,一种是不选;如0-1背包问题
遍历子集树的算法:
void Backtrack(int t) {//以深度优先的方式遍历第t层中的某棵子树 if(t>n) { Output(x); return; } if (……) { x[t]=1; Backtrack(t+1); } if (……) { x[t]=0; Backtrack(t+1); } }
3、排列树:当所给问题是确定n个元素的满足某种性质的排列时,相应的解空间树称为排列树。树的每一层列出上一层结点可以选择的可能;例:旅行售货员问题。
遍历排列树的算法
void backtrack (int t) { if (t>n) output(x); else for (int i=t; i<=n; i++) { swap(x[t], x[i]); if (constraint(t)&&bound(t)) backtrack(t+1); swap(x[t], x[i]); } }
4、回溯法还有一个特别需要注意的点就是减枝的操作;回溯法遍历所有的可能,正常情况下时间复杂度都会很大;但如果有好减枝操作,时间复杂度就会大大下降;减枝操作可以减少无效搜索
5、常见的减枝函数有用约束函数在扩展结点处剪去不满足约束的子树和用限界函数剪去得不到最优解的子树。