贪心算法-------部分背包问题

一,部分背包问题介绍

首先介绍下0-1背包问题。假设一共有N件物品,第 i 件物品的价值为 Vi ,重量为Wi,一个小偷有一个最多只能装下重量为W的背包,他希望带走的物品越有价值越好,请问:他应该选择哪些物品?

0-1背包问题的特点是:对于某件(更适合的说法是:某类)物品,要么被带走(选择了它),要么不被带走(没有选择它),不存在只带走一部分的情况。

0-1背包问题中的物品想象的一个金子,你要么把它带走,要么不带走它;而部分背包问题中的物品则是一堆金粉末,拿走一个表示带走一堆,也可以取任意部分的金粉末

二,部分背包问题的贪心算法

部分背包问题可以用贪心算法求解,且能够得到最优解。

贪心策略是什么呢?将物品按单位重量 所具有的价值排序。总是优先选择单位重量下价值最大的物品。

单位重量所具有的价值:Vi / Wi

举个例子:假设背包可容纳50Kg的重量,物品信息如下:

物品 i      重量(Kg)      价值           单位重量的价值

1             10          60                 6

2             20          100               5

3             30          120               4

按照我们的贪心策略,单位重量的价值排序: 物品1 > 物品2 > 物品3

因此,我们尽可能地多拿物品1,直到将物品1拿完之后,才去拿物品2.....

最终贪心选择的结果是这样的:物品1全部拿完,物品2也全部拿完,物品3拿走10Kg(只拿走了物品3的一部分!!!)

这种选择获得的价值是最大的。在(三)会给出证明。

而对于0-1背包问题,如果也按“优先选择单位重量下价值最大的物品”这个贪心策略,那么,在拿了物品1和物品2之后,就不能在拿物品3了。因为,在拿了物品1和物品2之后,背包中已经装了10+20=30Kg的物品了,已经装不下物品3了(50-30 < 30)(0-1背包:一件物品要么拿,要么不拿,否能只拿一部分),此时得到的总价值是 160。而如果拿物品2和物品3,得到的价值为220。这说明,该贪心策略对0-1背包问题,不能求得最优解。

三,部分背包问题的贪心策略的正确性证明

贪心策略是:总是优先选择单位重量下价值最大的物品

正确性证明 是:使用该贪心策略,可以获得最优解。在这里,最优解就是带走的物品价值最大。

证明思路:先考察一个全局最优解,然后对该解加以修改(一般是采用“剪枝”技巧),使其采用贪心选择,这个选择将原问题变成一个相似的、但是更小的问题。

先假设 物品集合S={W1,W2....Wn}已经按 单位重量价值从小到大排好序了。

并假设 一个全局最优解是:S(i)={Wi1,Wi2,.....Win}。Wi1,Wi2,.....Win是有序的。对于贪心选择而言,总是会优先 选择 Wn 的物品,当Wn 没有后,再选择Wn-1 .....

如果Win = Wn 问题已经得证。因为,我们的最优解S(i)中,已经包含了贪心选择。只要继续归纳下去,Wi(n-1) 就是 Wn-1 ....

如果Win != Wn 运用剪枝技巧,剪掉Win 并 贴上 Wn  此时,得到的是一个更优的解(因为价值更大了 ,Wn > Win)。因为,Wn 是单位重量价值最高的那个物品啊,我们的贪心选择应该选择它,但是这里的最优解S(i)却没有选择它,于是我们用剪枝技巧,将它加入到S(i)中去,并把S(i)中的Win除去。  

这就证明了,如果用贪心策略来进行选择,得到的是最优解。从而证明了贪心算法的正确性。

其实,也就是证明了一定存在一个最优解,这个最优解就是由贪心选择组成的。

四、部分背包问题算法实现

  1. //4d2 贪心算法 背包问题  
  2. #include "stdafx.h"  
  3. #include    
  4. using namespace std;   
  5.   
  6. const int N = 3;  
  7.   
  8. void Knapsack(int n,float M,float v[],float w[],float x[]);  
  9.   
  10. int main()  
  11. {  
  12.     float M = 50;//背包所能容纳的重量  
  13.     //这里给定的物品按单位价值减序排序  
  14.     float w[] = {0,10,20,30};//下标从1开始  
  15.     float v[] = {0,60,100,120};  
  16.   
  17.     float x[N+1];  
  18.   
  19.     cout<<"背包所能容纳的重量为:"<
  20.     cout<<"待装物品的重量和价值分别为:"<
  21.     for(int i=1; i<=N; i++)  
  22.     {  
  23.         cout<<"["<
  24.     }  
  25.       
  26.     Knapsack(N,M,v,w,x);  
  27.   
  28.     cout<<"选择装下的物品比例如下:"<
  29.     for(int i=1; i<=N; i++)  
  30.     {  
  31.         cout<<"["<
  32.     }  
  33.   
  34.     return 0;  
  35. }  
  36.   
  37. void Knapsack(int n,float M,float v[],float w[],float x[])  
  38. {  
  39.     //Sort(n,v,w);//这里假定w[],v[]已按要求排好序  
  40.     int i;  
  41.     for (i=1;i<=n;i++)  
  42.     {  
  43.         x[i]=0;//初始化数组x[]  
  44.     }  
  45.   
  46.     float c=M;  
  47.     for (i=1;i<=n;i++)//物品整件被装下,x[i]=1  
  48.     {  
  49.         if (w[i]>c)  
  50.         {  
  51.             break;  
  52.         }  
  53.         x[i]=1;  
  54.         c-=w[i];  
  55.     }  
  56.   
  57.     //物品i只有部分被装下  
  58.     if (i<=n)  
  59.     {  
  60.         x[i]=c/w[i];  
  61.     }  
  62. }  

     程序运行结果为:

     算法knapsack的主要计算时间在于将各种物品依其单位重量的价值从大到小排序。因此,算法的计算时间上界为O(nlogn)。为了证明算法的正确性,还必须证明背包问题具有贪心选择性质。这种贪心选择策略对0-1 背包问题就不适用了。看下图例子,其中有3中物品,背包的容量为50千克。物品1重10千克;价值60元;物品2重20千克;价值100元;物品3重30千克,价值120元。因此,物品1每千克价值6元,物品2每千克价值5元,物品3每千克价值4元。若依贪心选择策略,应首先选择物品1装入背包,然而从图b中各种情况可以看出,最优的选择方案是选择物品2和物品3装入背包。首选物品1的两种方案都不是最优的。对于背包问题,贪心选择最终可得到最优解,其选择方案如图c所示。

     对于0-1背包问题,贪心选择之所以不能得到最优解是因为在这种情况下,它无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。事实上,在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再作出最好选择。由此就导出许多互相重叠的子问题。这正是该问题可用动态规划算法求解的另一重要特征。

五,参考资料

https://www.cnblogs.com/hapjin/p/5575109.html

https://blog.csdn.net/qq_27231343/article/details/50934438

https://blog.csdn.net/qq_32400847/article/details/51336300

 从 活动选择问题 看动态规划和贪心算法的区别与联系 文章中讲到的 “活动选择问题”的贪心策略的正确性证明。二者证明思路基本一致。

http://www.cnblogs.com/hapjin/p/5573419.html

某种 找换硬币问题的贪心算法的正确性证明

http://www.cnblogs.com/hapjin/p/5575112.html

你可能感兴趣的:(贪心算法,算法)