算法解题部件:
- 根据问题定义解空间,这可以类似于概率论中的样本空间S,空间中包含所有的可能结果,多以n叉树实现;
- 约束条件,对解空间中的解做出约束,以缩减搜索解空间成本,避免不必要的搜索;
- 界限条件,根据约束条件,我们可以找到问题的所有可行解,当需要求得最优解是,我们需要设置界限条件,即当满足该条件时,我们可能找到最优解。
当我们需要找到所有可行解的时候,则只需要设定约束函数即可,若要求最优解,就需要两条件同时上阵了!
下面我们以对简单的0-1背包问题为例,学习回溯法:
问题分析&算法设计
很明显,在n个宝物中选择几个宝物带走,即从集合S中选择一个子集,要求子集的总重量不超过W,而且价值最大
针对于S中的每一个宝物,只会存在“拿”(1)与“不拿”(0)的问题,由此,我们可以得出解空间为{x1,x2,x3.....xn},其中xi=0/1;不难看出,解空间中一共有2^n种可能解,我们可以将解空间定义为深度为n的二叉树)(取左为1,取右为0)。
约束条件:
- 搜索过程中,沿着拓展节点的左分支拓展,表示装入宝物。由此可知,我们在拓展时应该判断约束条件是否成立,成立则左,不成立则右。
- 在每个节点判断界限条件,若不成立,则该节点为死结点,无须向下搜索。
代码示例:
#include <iostream> #include <string> #include <algorithm> #define M 105 using namespace std; int i, j, n, w; //n表示n个物品,w表示船容量 double W[M], V[M]; bool x[M]; double cw, cp, bestp; //cw,cp为当前船重量与价值,bestp为当前为止最优的价值记录 bool bestx[M]; //当前最优问题解 //计算已确定的物品和剩余的物品总价值 double Bound(int i) { int rp = 0; while (i <= n) { rp += V[i]; i++; } return cp+rp; } //用于 void Backtrack(int t) { if (t > n)//到达树末的叶子 { for (j = 1; j <= n; j++) { bestx[j] = x[j]; //保存当前最优解,为什么不用判断就直接保存呢?因为有Bound的关系,能走到叶子,必然比之前的最优解更优 } bestp = cp; return; } if (cw + W[t] <= w) { x[t] = 1; cw += W[t]; cp += V[t]; Backtrack(t + 1); cw -= W[t]; cp -= V[t]; } if (Bound(t+1) > bestp) //判断是否搜索右子树 { x[t] = 0; Backtrack(t + 1); } } void Knapsack(double w, int n) { //初始化 cw = 0; cp = 0; bestp = 0; double sumw = 0.0; double sumv = 0.0; //用于记录所有物品的总重量和总价值 for (i = 1; i <= n; i++) { sumw += W[i]; sumv += V[i]; } if (sumw <= w) { bestp = sumv; cout << "所有物品均放入背包,最大价值为: " << bestp << endl; return; } //开始搜索 Backtrack(1); cout << "放入船的最大价值为: " << bestp << endl; cout << "放入的物品的序号为: "; for (i = 0; i <= n; i++) { if (bestx[i] == 1) { cout << i << " "; } } cout << endl; } int main() { cout << "请输入物品的个数n: "; cin >> n; cout << "请输入船的容量w: "; cin >> w; cout << "请依次输入每个物品的重量和价值,以空格分开: "; for (i = 1; i <= n; i++) { cin >> W[i] >> V[i]; } return 0; }
文章来源: https://blog.csdn.net/wangtao990503/article/details/88590817