【软考总结】-<算法>动态规划法--0-1背包问题

来源:互联网 发布:淘宝知识产权侵权案例 编辑:程序博客网 时间:2024/06/11 11:56

    在上篇博客中,我们讲了动态规划法在最长公共子序列问题中的应用,(详情请见博客《算法:动态规划法--最长公共子序列》)。这次学习了0-1背包问题,对动态规划法的思想理解了更深了一层,以下是我的简单理解,希望能为路过的读者带来帮助。

第一,动态规划法思想

    1、简单了解分治法和贪心法

    分治,分治,分而治之。分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立与原问题性质相同求出子问题的解,就可得到原问题的解贪心法,基本思想是整体最优解可以通过一系列局部最优得到,所以只需得到局部最优解,每一步都只考虑一个数据,所以不一定是整体最优。   

    2、三法相比,理解动态规划法

    贪心法的特征是由顶向下,一步一步地做出贪心选择,但不足的是,如果当前选择可能要依赖子问题的解时,则难以通过局部的贪心策略达到全局最优解。相比而言,动态规划则可以处理不具有贪心实质的问题。

    而用分治法解决问题时,由于子问题的数目往往是问题规模的指数函数,因此对时间的消耗太大。动态规划的思想在于,如果各个子问题不是独立的,不同的子问题的个数只是多项式量级。如果我们能够保存已经解决的子问题的答案,而在需要的时候再找出已求得的答案,这样就可以避免大量的重复计算。由此而来的基本思路是,用一个表记录所有已解决的子问题的答案,不管该问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思想。

第二,动态规划法求解0-1背包问题

                                          

 ***************************************问题描述********************************************

   n个物品,第i个物品价值为vi,重量为wi,其中viwi均为非负数,背包的容量为WW为非负数。现需要考虑如何选择装入背包的物品,使装入背包的物品总价值最大。该问题以形式化描述如下:

   目标函数为 :  

   约束条件为:

   满足约束条件的任一集合(x1,x2,...,xn)是问题的一个可行解,问题的目标是要求问题的一个最优解。考虑一个实例,假设n=5,W=17, 每个物品的价值和重量如表9-1所示。可将物品1,2和5装入背包,背包未满,获得价值22,此时问题解为(1,1,0,0,1)。也可以将物品4和5装入背包,背包装满,获得价值24,此时解为(0,0,0,1,1)。

*********************************动态规划法求解过程*****************************************   

   1、刻画最优解结构

    背包求解过程可看做进行一系列决策的过程,即决定哪些物品应该放入背包,哪些不放入。不能物品i装入多次,也不能只装入物品的一部分。如果一个问题的最优解包含了物品nXn=1,那么,其他X1X2…Xn-1一定构成子问题1,2,3…n-1在容量为W-wn时的最优解。如果不包含物品nXn=0,那么,其他X1X2…Xn-1一定构成子问题1,2,3…n-1在容量为W时的最优解,即在此容量下,不放这个物品。

    2、递归定义最优解的值

    c[I,w]表示背包容量为wi个物品得到的最优解的总价值。显然,我们是要求解c[n,w]。(这里注意,不是第i个物品,是背包里存在i个物品)


    3、计算背包问题最优解的值。

  (为了让软考的小朋友更好的理解,此代码复制自《软件设计师教程》第4版复制)

C语言代码:

int** KnapsackDP(int n,int W,int* Weights,float* Values) { int i, w ; / * 为 二 维 数 组 申 请 空 间 * / int** c = (int **)malloc(sizeof(int * ) *(n + l)); for (i = 0 ; i < = n; i++) c[i]= (int *)malloc(sizeof(int)*(W + l)); / * 初 始 化 二 维 数 组 * / For(w=0;w <W;w++)    c[0][w] = 0; //i=0的一行值全为0;for(i=1;i<= n;i++) {                    //i在外循环,在i一定时,随着背包容量增加,是否放该物品   c[i][0] = 0;                        //w=0的一整列全为0;for(w = 1 ;w < = W;w++) { if(Weights[i-1] < = w) { / * 如 果 背 包 剩 余 重 量 大 于 物 品 重 量 if(Values[i-1]+c[i-1][w-Weights[i-1]]>c[i-1][w]){ /* 重 量 为 w 的 背 包 中有 该 物 品 * / c[i][w] = Values[i-1] + c[i-1][w -Weights[i-1]];}else{ /*重 量 为 w 的 背 包 中没有 该 物 品 * / c[i][w]  = c[i-1][w]; }}Elsec[i][w]  =c[i-1][w];}}Return c ;}
   时间复杂度:O(nW)
得到记录表格:

         

    4、根据计算结果构造问题最优解

   

Void OutPutKnapsackDP(int n,int w,int *Weights,float *values,int**c) { Int x[n] ; int 1 ; For (i=n;j>1;i--) {    if(c[i][w]==c[i-1][w]) / * 重 量 为 w 的 最 优 选 择 的 背 包 中 不 包 含 该 物 品 */       x[i-1] = 0 ;    Else{      x[i-1] = 1 ;      W =W-Weightst[i-1]; / * 更 新 背 包 目 前 的 最 大 容 量 * /    }}if(c[1][W]=0) / * 第 一 个 物 品 不 在 背 包 * /       x[0] =0; else / *第 一 个 物 品在背 包中* /        x[0] =1;For(i=0;i<n;i++)      if(x[i]==1)            printf("Weight:%d,Value:%f\n",Weights[i],Values[i]);}

   时间复杂度是:O(n)

注:

C语言中

%d,用来输出十进制整数

%f,用来输出实数(包括单,双精度),以小数形式输出


****************************************举例说明********************************************

    当背包容量为8w=8),可以放2个物品时(i=2),背包的最大价值是多少?

1、判断背包的容量是否可以容下此物品:

     Weights[i-1] =Weights[1]=4,(因为数组的下标从0开始,所以这里指第二个物品的重量是4),4<8,第二个物品可以放入背包。

2、根据价值,判断是否将此物品放入背包

    假设此物品已经放入背包,那么根据刻画的最优结构“如果一个问题的最优解包含了物品nXn=1,那么,其他X1X2…Xn-1一定构成子问题1,2,3…n-1在容量为W-wn时的最优解。”计算价值

   Values[i-1]+c[i-1][w-Weights[i-1]]=Values[1]+c[1][4]=9

   即,第二个物品的价值5+背包容量位(8-4)时,放两个物品时的价值(此情况在之前已经计算出,c[1][4]=4

第三,总结

    在什么情况下可以使用动态规划法?1、有最优子结构:一个问题有很多解,每个解对应一个值,我们希望找到最优解;2、重叠子问题:每个子问题之间不是相互独立的,求解第二个问题,可能用到第一个问题的解。

结合最长公共子序列问题和0-1背包问题,适合动态规划法求解的问题,经分解得到的子问题保存已解决的子问题的答案,在需要时再找出已求得的答案,这样可以避免大量的重复计算,从而得到多项式时间的算法。



0 0
原创粉丝点击