编程之美1.6 饮料供货[动态规划vs贪心算法]

饮料供货是一个 求最优解问题 。需要在给定最大容量V的前提下,从不同容量不同满意度的饮料中选择满意度最大的集合。

1. 动态规划

动态规划是最常用的解决最优化问题的方法,很容易应用到本题的需求中。用f[V,i]表示从第i,i+1,i+2,...,n-1种饮料中,算出总量为V的方案中满意度之和的最大值。

动态规划方程为:f[V,i] = max{k*Hi + f[V-k*Vi, i+1]}

弄清解法之后,我们不妨来分析一下问题的最优子结构

根据《算法导论》描述,最优子结构有两种变形:

     1) 有多少个问题在原问题的最优解中,

     2) 在决定一个最优解中使用哪些子问题时有多少个选择。

在饮料供货问题中,一个最优解只使用了一个子问题,但为了确定最优解,我们必须考虑Ci(第i中饮料可选的最大数量)种选择。

 

2. 贪心算法

书中提到第二种解法,贪心算法。由于所有饮料的容量都是2的整数幂,这就给贪心创造了条件。

假设给定的容量为V,我们可以把V写成二进制的形式,不妨设V=7,二进制的写法为111。

接下来我们就从最低位开始取:

第一步,取第一个1:拿出容量为1满意度最大的饮料,

第二步,取第二个1:使用剩余容量为1的饮料构造容量为2的饮料,比如(1,2)(1,3)构造出(2,5),并从新构造的和原有容量为2的所有饮料中选出满意度最大的。

第三步,取第三个1:递归构造容量为4的饮料,并从新构造的和原有容量为4的所有饮料中选出满意度最大的。

 

3.定义饮料类

首先需要定义一个饮料的类:

[cpp]  view plain copy
  1. struct Drink  
  2. {  
  3.     int Volume;  
  4.     int TotalCount;  
  5.     int Happiness;  
  6.     Drink(int v, int t, int h)  
  7.     {  
  8.         Volume = v;  
  9.         TotalCount = t;  
  10.         Happiness = h;  
  11.     }  
  12. };  

建立一个存储饮料的全局动态数组,用于存放所有饮料,由于同时兼顾动态规划和贪心算法两种方法,我们使用了一个二维数组。

drink[i][j]表示容量为2^i的第j个饮料。

[cpp]  view plain copy
  1. vector<vector<Drink> > drinks;  
  2. void initialize()  
  3. {  
  4.     ifstream fin("drink.txt");  
  5.     int v,t,h;  
  6.       
  7.     while(fin>>v>>t>>h)  
  8.     {  
  9.         while(drinks.size()<=v)  
  10.         {  
  11.             drinks.push_back(vector<Drink>());  
  12.         }  
  13.         drinks[v].push_back(Drink((int)pow(2,v),t,h));  
  14.     }  
  15.     fin.close();  
  16. }  

创建样例输入drink.txt。

[plain]  view plain copy
  1. 0 2 2  
  2. 0 2 3  
  3. 0 3 5  
  4. 0 3 4  
  5. 1 2 6  
  6. 1 3 5  
  7. 1 4 4  
  8. 2 1 18  
  9. 2 2 12  

 接下来使用两种方法求解当V=7时的最高满意度值。

 

4.动态规划解法

[cpp]  view plain copy
  1. const int MAXV=101;  
  2. const int MAXT=101;  
  3. int opt[MAXV][MAXT];  
  4.   
  5. // 动态规划   
  6. int GetMaxHappinessByDP1(int V)  
  7. {  
  8.     int maxHappiness = 0;  
  9.     vector<Drink> temp;  
  10.     for (int i=0; i<drinks.size(); ++i)  
  11.         for(int j=0; j<drinks[i].size(); ++j)  
  12.             temp.push_back(drinks[i][j]);  
  13.   
  14.     int T = temp.size();  
  15.   
  16.     // init  
  17.     for(int i=0; i<=T; ++i)  
  18.         opt[0][i] = 0;  
  19.     for (int i=0; i<=V; ++i)  
  20.         opt[i][T] = 0;  
  21.   
  22.     for (int i=T-1; i>=0; --i)  
  23.     {  
  24.         for(int j=0; j<=V; ++j)  
  25.         {  
  26.             opt[j][i] = 0;  
  27.             for (int k=0; k<=temp[i].TotalCount; ++k)  
  28.             {  
  29.                 int v = temp[i].Volume*k;  
  30.                 int h = temp[i].Happiness*k;  
  31.                 if (v<=j && opt[j-v][i+1]+h>opt[j][i])  
  32.                 {  
  33.                     opt[j][i] = opt[j-v][i+1]+h;  
  34.                 }  
  35.             }  
  36.         }  
  37.     }  
  38.     return opt[V][0];  
  39. }  


动态规划数组输出结果:

V

0

1

2

3

4

5

6

7

8

9

0

0

0

0

0

0

0

0

0

0

0

1

5

5

5

4

0

0

0

0

0

0

2

10

10

10

8

6

5

4

0

0

0

3

15

15

15

12

6

5

4

0

0

0

4

19

19

19

18

18

18

18

18

12

0

5

23

23

23

22

18

18

18

18

12

0

6

28

28

28

26

24

23

22

18

12

0

7

33

33

33

30

24

23

22

18

12

0

 只需要读取opt[7][0]即可获得最高满意度为33。

 

5.贪心解法

[cpp]  view plain copy
  1. int TakeOutMax( int k)  
  2. {  
  3.     int maxHappiness = 0;  
  4.     if (k < 0) return 0;  
  5.       
  6.     if (k > 0){  
  7.         int t =k/2;  
  8.         int h = TakeOutMax(t);  
  9.         if (h>0){  
  10.             drinks[t].push_back(Drink((int)pow(2,t), 1, h));  
  11.             h = TakeOutMax(t);  
  12.             if (h>0)  
  13.             {  
  14.                 drinks[t].push_back(Drink((int)pow(2,t), 1, h));  
  15.             }  
  16.         }  
  17.         int p1, p2;  
  18.         p1 = p2 = -1;  
  19.         for (int i=0; i<drinks[t].size(); i++)  
  20.         {  
  21.             for (int j=i+1; j<drinks[t].size(); ++j)  
  22.             {  
  23.                 if (drinks[t][i].TotalCount>0 && drinks[t][j].TotalCount>0 && drinks[t][i].Happiness+drinks[t][j].Happiness>maxHappiness)  
  24.                 {  
  25.                     maxHappiness = drinks[t][i].Happiness+drinks[t][j].Happiness;  
  26.                     p1 = i;  
  27.                     p2 = j;  
  28.                 }  
  29.             }  
  30.         }  
  31.         if (p1 >-1 && p2 > -1)  
  32.         {  
  33.             drinks[t][p1].TotalCount--;  
  34.             drinks[t][p2].TotalCount--;  
  35.             drinks[k].push_back(Drink((int)pow(2,k), 1, maxHappiness));  
  36.         }  
  37.     }  
  38.     int p=-1;  
  39.     maxHappiness = 0;  
  40.     for (int i=0; i<drinks[k].size(); ++i)  
  41.     {  
  42.         if (drinks[k][i].TotalCount>0 && drinks[k][i].Happiness>maxHappiness)  
  43.         {  
  44.             maxHappiness = drinks[k][i].Happiness;  
  45.             p = i;  
  46.         }  
  47.     }  
  48.     if (p >=0 ){  
  49.         drinks[k][p].TotalCount--;  
  50.     }  
  51.     return maxHappiness;  
  52. }  
  53.   
  54. // 贪心算法  
  55. int GetMaxHappinessByGreed(int V)  
  56. {  
  57.     int k = V;  
  58.     int i=0;  
  59.     int happiness = 0;  
  60.     while(k)  
  61.     {  
  62.         if (k & 1) happiness += TakeOutMax(i);  
  63.         k >>= 1;  
  64.         i++;  
  65.     }  
  66.     return happiness;  
  67. }  

贪心解法中TakeOutMax()函数会改变drinks数组。

最后的返回值是一样的:33。

 

6.总结

1)动态规划解法中,前两层循环的次序可以颠倒。也就是在生成的动态数组中,我们可以从上往下一行一行地填,或者从右往左一列一列地填都不影响最后结果,最关键的是需要事先把边界值设定好,最上面一行全为0,最右边一列全为0。

2)贪心算法的思路其实并没有动态规划清晰,但也容易想通,算法的复杂度我没有仔细研究,欢迎大家讨论。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值