$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Self-defined math definitions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Math symbol commands \newcommand{\intd}{\,{\rm d}} % Symbol 'd' used in integration, such as 'dx' \newcommand{\diff}{{\rm d}} % Symbol 'd' used in differentiation ... $$

【算法学习】- 神奇的背包(贪心算法)

神奇的背包

前言

问题描述:已知容量为 M 的背包,现有 n 件物品。第 i 件物品的重量为 w,价值是 p。可以将物品 i 的一部分装入背包从而获得 pixi 的价值。现在如何装入背包,才能获得最大的价值?( 也可以称之为“部分背包问题” )

问题的现实表述:某日,小明前往百货超市购买生活用品,恰好赶上超市周年庆。超市为答谢新老客户的支持,于是提出了一项活动:每个人发放一个总承重为 M 的袋子,现在顾客可以前往散装区,散装区中有 n 种商品,任意的向袋子中装入物品,只要装入的物品不使得袋子破裂,最后结账时,这一袋子物品免费。此时小明作为该店的优秀老客户,准备前往散装区大显身手,可问题来了,装东西的办法千千万,该怎么选呢?

此时,一种名为贪心算法的方式浮现在了小明的脑海中。

贪心算法

  贪心算法(greedy algorithm),又名贪婪法。顾名思义,在每一次进行决策时,总是做出在当前看来是最好的选择(局部最优解)。由于贪心算法并未考虑整体最优,所以所作出的选择是在某种意义上的局部最优。但事实上,有相当一部分问题,采用贪心算法是可以得到整体最优解的。  

  贪心算法主要用于处理优化问题。每个优化问题都是由目标函数和约束条件组成,满足约束条件的解称为可行解,使得目标函数取得最大(最小)的可行解称为最优解。

  贪心算法虽然在每一步的决策中并没有完全顾及问题的整体最优,但在局部最优中则是朝着整体最优发展的。为此,贪心算法首先要确立一个度量准则(贪心准则/贪心策略),每一步都按照这个准则选取优化方案。对于一个给定的问题,初看,往往有若干种贪心策略可选,但在实际上,大多数的策略并不能得到问题的最优解。实践证明,选择能产生最优解的贪心策略是设计贪心算法的核心问题。

 

 策略的选择

  小明想到了贪心算法,于是决定通过这种算法来选定装包方案。现在,出现在小明脑海里的有三种方案:

  1. 重量贪心策略:每次先捡最轻的物品装。
  2. 价值贪心策略:每次先捡最贵的物品装。
  3. 价值重量比贪心策略:每次先捡单位价值最大的物品装。

  我们在“贪心算法”小节中提过,纵使有若干种策略,但实际上,绝大多数的策略并不能得到整体最优解。上边的三种策略中,前两种就无法得到全局最优解。下面举例说明:现有一个背包,其容量 M = 20,一共三种物品,即 n = 3,价值向量为 p =( 25, 24, 15 ),重量向量为 w = ( 18, 15, 10 )。若采用重量贪心策略,首先将物品 3 装包,此时获得的效益为 15 且背包余量为 10,此时只能装入物品 2 的 2/3,此时获得的效益为 24 * 2/3 = 16,所以在重量贪心策略下的总效益为 15 + 16 = 31;若采用价值贪心策略,首先将物品 1 装包,获得的效益为 25 且背包余量为 2,此时只能装入物品 2 的 2/15,此时获得的效益为 24 * 2/15 = 3.2,所以在价值贪心策略下的总效益为 25 + 3.2 = 28.2;若采用价值重量比贪心策略,则物品的单位价值向量为  (1.39, 1.6, 1.5 ),首先将物品 2 装包,此时获得的效益为24且背包余量为 5;此时只能装入物品 3 的 1/2,此时获得的效益为 15 * 1/2 = 7.5,所以在价值重量比贪心策略下的总效益为 24 + 7.5 = 31.5。所以根据策略 1 和策略 2 所得到的可行解 ( 0, 2/3, 1 ) 和 ( 1, 2/15, 0) 都不是该问题的最优解。所以,这么看来相对合理的策略是第三种,而事实上,按照价值重量比贪心策略来装,也的确能够达到总价值的最大。【接下来,会有较长的部分给到该贪心策略正确性的证明,我会尽可能的详细书写每一步过程,但难免会有不清楚的地方,请多多见谅。此处使用的方法为交换论证法】

 伪代码

GreedyKnapsack( p, w, x, n, M )    //传入的元素满足价值重量比依次递减的原则
    float rc
    x := 0    //解向量初始化为0
    rc := M    //背包的剩余容量初始化为M
    for i to n do    //按照贪心策略,将能装的全部装进去
        if w[i] <= rc then
            x[i] := 1; rc := rc - w[i]
        else
            break
        end{ if }
    end{ for }
    if i <= n then
        x[i] := rc / w[i]
     end{ if }
end{ GreedyKnapsack }

C++代码

 


#include<iostream>
using namespace std;


/*构造排序函数,将输入的价值向量 P 和重量向量 w 按照价值重量比从大到小进行排序*/
void Sort( int w[], int p[], int N ){
int i, j, int_temp;
float float_temp;
float v[ N ] = { 0 }; //用 v[]存储每一种商品的价值重量比

for( i = 0; i < N; i++ ){
v[ i ] = (double)p[ i ] / w[ i ]; //计算每种商品的价值重量比并保存至 v[]数组当中
}

for( i = 0 ; i < N ; i++ ){ //按照 v[]中的数据将 p[]和 w[]按照从大到小进行排序
for ( j = i + 1; j < N; j++ ){
if( v[ i ] < v[ j ] ){
float_temp = v [ i ];
v[ i ] = v[ j ];
v[ j ] = float_temp;
int_temp = w [ i ];
w[ i ] = w[ j ];
w[ j ] = int_temp;
int_temp = p [ i ];
p[ i ] = p[ j ];
p[ j ] = int_temp;
}
}
}
}


/*按照贪心策略的解决部分背包问题的核心算法 */
void greedy_knapsack( int w[], int p[], int N, float result[], float W ){
float temp = 0;
int i = 0;
Sort(w, p, N);
for( i = 0; i < N; i++ ){
if( W > w[ i ] ){
W = W - w[ i ];
result[ i ] = 1;
}
else if( W - 0 > 0.0001 ){
result[ i ] = (double)W / w[ i ];
W = 0;
return;
}
}
int main(){

int N = 3; //物品的数量
float W = 20.0; //背包大小
float result[3]; //存放每个物品放入背包的多少
int w[] = { 18, 15, 10 }; //价值向量
int p[] = {25, 24, 15 }; //重量向量
float max_benefit = 0; //保存最后背包装满时总的最大效益

greedy_knapsack( w, p, N, result, W );

for( int i =0; i < N; i++ )
if( result[ i ] < 0.0001)
result[ i ] = 0;
cout << "w[] " << "p[] " << "reslut[] " << endl;
for( int i = 0; i < N; i++ ){
cout << w[ i ] << " " << p[ i ] << " " << result[ i ] << endl;
max_benefit += p[ i ] * result[ i ];
}
cout << "最大效益是:" << max_benefit << endl;
return 0;
}
/* 此程序在 Dev-C++上进行编译时,输出的大小为 1.84MB,编译时间为0.48s,后续会在该基础上对于一些部分进行再优化,期待能更加快速高效的利用贪心算法解决部分背包问题*/

 

 

参考资料

[1] 《计算机算法设计与分析》,中国科学院大学,刘玉贵。

[2]  部分背包问题(贪心算法),CSDN,https://blog.csdn.net/doubleguy/article/details/102980460

[3] 部分背包问题,C语言中文网,http://c.biancheng.net/algorithm/fractional-knapsack.html

[4] 贪心算法,百度百科,https://baike.baidu.com/

posted @ 2022-10-24 13:41  素衣叹风尘  阅读(428)  评论(0)    收藏  举报