算法第五章作业
一、对回溯法的理解
1、概念:
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
2、问题的解空间:
复杂问题常常有很多的可能解,这些可能解构成了问题的解空间。解空间也就是进行穷举的搜索空间,所以,解空间中应该包括所有的可能解。
例如,对于有n个物品的0/1背包问题,当n=3时,其解空间是:
{(0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0), (1, 1, 1) }
- 子集树:当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树成为子集树。例:0-1背包问题。
- 排列树:当所给问题是确定n个元素的满足某种性质的排列时,相应的解空间树称为排列树。例:旅行售货员问题。
3、基本步骤:
(1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
4、常用剪枝函数:
(1)用约束函数在扩展结点处剪去不满足约束的子树;
(2)用限界函数剪去得不到最优解的子树。
注:问题的解空间树是虚拟的,并不需要在算法运行时构造一棵真正的树结构,只需要存储从根结点到当前结点的路径。
二、“子集和”问题的解空间结构和约束函数
1、解空间结构:
解空间为正整数集合S={x1,x2,…,xn}子集中,所有元素之和为正整数c的子集的集合。
2、约束函数:
如果已选择数之和加上当前数小于c,则进入右子树继续搜索,否则舍弃当前数,进入左子树。
代码实现:
#include<iostream> using namespace std; int n, c; int s = 0; int flag = 0; int a[10001], b[10001]; int bound1(int t){ int b = s; for(int i=t; i<=n; i++){ b += a[i]; } return b; } void backtrack1(int t){ if(flag){ return; } if(t > n){ if(s == c){ flag = 1; for(int i=1; i<=n; i++){ if(b[i] == 1){ cout << a[i] << " "; } } cout << endl; return; } } else { if(s + a[t] <= c){ b[t] =1; s += a[t]; backtrack1(t+1); s -= a[t]; b[t] = 0; } if(bound1(t+1) >= c){ backtrack1(t+1); } } } int main(){ cin >> n >> c; for(int i=1; i<=n; i++){ cin >> a[i]; b[i] = 0; } backtrack1(1); if(flag == 0){ cout << "No Solution!" << endl; } return 0; }
三、在本章学习过程中遇到的问题及结对编程的情况
1、问题:
感觉比较难想到问题解空间数该如何构建以及怎样剪枝才可以比较好地提高效率,尤其是排列数
2、结对编程情况:
两个人想得会更全面一些,能够在相互地磨合中发现自己地不足、学习对方的优点,相比于两个人,一个人会总是容易钻牛角尖,在死胡同里出不来,这个时候就需要伙伴用一个新的角度来提供想法和建议。