0-1背包问题与分数背包问题

  • 0-1背包问题与分数背包问题

    • 问题描述
    • 问题分析之分数背包
    • 代码设计之分数背包问题
    • 问题分析之0-1背包问题
    • 代码设计之0-1背包问题
    • 动态规划算法之间的差别

0-1背包问题与分数背包问题

我们在文章《贪心算法原理》:http://blog.csdn.net/ii1245712564/article/details/45369491中提到过动态规划和贪心算法的区别。以及两个经典的例子:0-1背包问题分数背包问题,我么知道0-1背包问题是不能够使用贪心算法求解的,而贪心算法则是分数背包问题的不二之选。

这次我们来着重实现一下0-1背包问题的动态规划解法以及分数背包问题的贪心算法。

问题描述

  • 0-1背包问题:我们有一堆物品S={a1,a2,...,an},每一个物品ai都有一个重量wi和一个价值vi.现在有一个背包,这个背包的容量为W,现在要将这些物品在不超出背包容量的情况下选择性的放入背包,使得背包里面物品的价值最大,物品不能只选取其中一部分,必须选择整个,或者不选!
  • 分数背包问题:这个问题和上面的问题比较相似,唯一不同的就是该问题里面的物品可以进行分割,即可以只选取一个物品ai的一部分

这里我们采用例子:

我们有三个物品和一个容量为50的背包,这三个物品<重量,价值>分别为:a1<10,60>,a2<20,100>,a3<30,120>.

问题分析之分数背包

为简单期间,我们首先来分析一下分数背包问题。如果要设计一个贪心算法,那么首先要确定一个贪心策略,换句话说就是要怎么在当前的情况下做出一个合理的贪心选择。

我们首先得到每一个物品单位重量的价值vi/wi,那么我们要设计一个贪心策略来使得装入背包物品的价值最大。我们的第一直觉肯定是要选择单位重量价格最高的喽,然后再选择物品里面第二高的,一次类推直到装满背包为止!

下面我们来证明一下上面贪心选择的猜想:

证明:

我们首先假设我们有一个最优解A1,那么我们首先找到A1里面平均价值最高的物品am,让后我们将用商品里面平均价值最高的物品a1将am进行全部替换或者部分替换得到解A2,又因v1/w1≥vm/wm所以A2的总价值高于A1的总价值,这A1是最优解矛盾,于是得到A1里面包含平均价值最高的物品。

于是我们得到最优子结构Si=Sk+ak,ak是Si里面平均价值最高的,Sk是选择ak剩下来的物品。

代码设计之分数背包问题

依照我们上面描述的分数背包问题的最佳贪心策略,每次都选出平均价值最高的物品

/*************************************************
* @Filename:    fractionPackage.cc
* @Author:      qeesung
* @Email:       [email protected]
* @DateTime:    2015-04-30 14:44:28
* @Version:     1.0
* @Description: 贪心策略,分数背包问题
**************************************************/

#include <iostream>
#include <utility>
#include <vector>

using namespace std;

#define PACKAGE_CAPACITY 50

/**
 * 求得goodslist对应的最大价值,我们首先假设物品按照平均价值降序排序
 * @param  goodsList 商品列表
 * @return           最大价值
 */
unsigned fractionPackage(std::vector<pair<unsigned , unsigned> > goodsList)
{
    unsigned valueSum=0;
    unsigned capacitySum=0;
    for (int i = 0; i < goodsList.size(); ++i)
    {
        // 转完这次就退出
        if(goodsList[i].second + capacitySum >= PACKAGE_CAPACITY)
        {
            valueSum+=(PACKAGE_CAPACITY - capacitySum)*(goodsList[i].first/goodsList[i].second);
            return valueSum;
        }
        valueSum+=goodsList[i].first;
        capacitySum+=goodsList[i].second;
    }
    return valueSum;
}

int main(int argc, char const *argv[])
{
    std::vector<pair<unsigned , unsigned> > goodsList;
    goodsList.push_back(pair<unsigned , unsigned>(60,10));
    goodsList.push_back(pair<unsigned , unsigned>(100,20));
    goodsList.push_back(pair<unsigned , unsigned>(120,30));
    cout<<"max value is : "<<fractionPackage(goodsList)<<endl;
    while(1);
    return 0;
}

运行结果为:

max value is 240

我们这里首先选择平均价值最高的a1放入背包,然后放入a2,因为此时a3不能全部放入背包,于是我们放入a3的一部分进入到背包。这里也很好理解,那就是我们在物品总能装满背包的情况下,背包总是可以被装满的,我们如果要使总价值最高,那我们就应该提升平均的价值密度,那就把平均价值最高的依次放入背包!

问题分析之0-1背包问题

为什么我们不能用贪心算法来解决0-1背包问题呢,我们只需要举一个反例就可以了,我们还是按照之前的将平均价值最大的放入背包里面,放入a1,然后a2,a3已经不能再放入了,于是我们得到背包物品总价值为60+100=160,但是这个是最优解么?肯定不是,因为只放入a2和a3总价值就达到100+120=220,所以这里我们只能另辟蹊径。

每一个物品只有两种选择,那就是放入背包与不放入背包。所以我们要做的就是决定一个物品是放入还是不放入背包。

在求解动态规划问题中,我们首先要做的,就是找到最优子结构递归表达式。于是我们假设f(i,W)为有i,i+1,i+2,...,nn?i+1个商品,背包容量为W的最优解。那么我们首先需决定的就是ai这个物品到底要不要放入到背包里面,决定这个标准是什么?

  • 如果wi≤W,那么物品ai是可以放入背包的,如果我们放入背包,那么价值为f(i,W)=f(i+1,W?wi)+vi,要是不放入背包,那么f(i,W)=f(i+1,wi),我们选择值最大的那个!
  • 如果wi>W,那么ai不能放入到背包里面,于是没有别的选择,f(i,W)=f(i+1,W)

从上面我们看到了我们一直想要的,最优子结构

我们得到对应的递归表达式为:

f(i,W)={f(i+1,W)f(i+1,W?wi)+vi,wi>W,wi≤W

动态规划有了递归表达式啥都好说!

代码设计之0-1背包问题

有了上面的递归表达式,我们就可以开始敲代码啦!

我们这里采用两种动态规划的方法来求解,自上而下和自下而下,他们之间的细微差别我们在看完代码之后再指出

自上而下的动态规划算法

/*************************************************
* @Filename:    zeroOnePackage.cc
* @Author:      qeesung
* @Email:       [email protected]
* @DateTime:    2015-04-30 10:49:31
* @Version:     1.0
* @Description: 典型的0-1背包问题,采用动态规划自上而下求解
**************************************************/

#include <iostream>
#include <utility>
#include <vector>

using namespace std;

/** 背包的容量 */
#define PACKAGE_CAPACITY 50
/** 最多有十个商品 */
#define MAX_GOODS_NUM 10
/** 用来保存商品没有没有选中过 */
bool packageChoose[MAX_GOODS_NUM];
/** 用来存储对应的最大价值 maxValue[i][w]表示后面i个商品装入w容量背包最大价值 */
unsigned maxValue[MAX_GOODS_NUM][PACKAGE_CAPACITY];

unsigned dealZeroOnePackage(unsigned pos , unsigned weight ,     std::vector<pair<unsigned , unsigned> > & goodsList);
/**
 * 求得goodslist对应的最大价值
 * @param  goodsList 商品列表
 * @return           最大价值
 */
unsigned zeroOnePackage(std::vector<pair<unsigned , unsigned> > goodsList)
{
    for (int i = 0; i < MAX_GOODS_NUM; ++i)
    {
        packageChoose[i]=false;
    }
    dealZeroOnePackage(0,PACKAGE_CAPACITY,goodsList);
    return maxValue[0][PACKAGE_CAPACITY];
}

/**
 * 具体的递归求解
 * @param  pos       开始位置
 * @param  weight    剩余容量
 * @param  goodsList 商品列表
 * @return           对应pos开始容量为weight的最大值
 */
unsigned dealZeroOnePackage(unsigned pos , unsigned weight ,     std::vector<pair<unsigned , unsigned> > & goodsList)
{
    if(pos >= goodsList.size() || weight == 0)
        return 0;
    if(maxValue[pos][weight] != 0 )
        return maxValue[pos][weight];
    // 下面开始递归求解
    unsigned temp1 = dealZeroOnePackage(pos+1,weight , goodsList);
    if(weight >= goodsList[pos].second)
    {
        unsigned temp2 = dealZeroOnePackage(pos+1, weight - goodsList[pos].second, goodsList)+                         goodsList[pos].first;
        unsigned retValue = temp1>temp2?temp1:temp2;
        /** 判断pos位置到底选不选 */
        if(temp1 <= temp2)
        {
            packageChoose[pos]=true;
        }
        maxValue[pos][weight]= retValue;
        return retValue;
    }
    else
    {
        maxValue[pos][weight]=temp1;
        return temp1;
    }
}

void printChoose()
{
    for (unsigned i = 0; i < MAX_GOODS_NUM ; ++i)
    {
        if(packageChoose[i])
            cout<<"a"<<i<<"\t";
    }
    cout<<endl;
}

int main(int argc, char const *argv[])
{
    std::vector<pair<unsigned , unsigned> > goodsList;
    goodsList.push_back(pair<unsigned , unsigned>(60,10));
    goodsList.push_back(pair<unsigned , unsigned>(100,20));
    goodsList.push_back(pair<unsigned , unsigned>(120,30));
    cout<<"max value is : "<<zeroOnePackage(goodsList)<<endl;
    printChoose();
    while(1);
    return 0;
}

运行以后我们得到结果:

max value is 220

a1 a2

自下而上的动态规划算法

/*************************************************
* @Filename:    zeroOnePackage_v2.cc
* @Author:      qeesung
* @Email:       [email protected]
* @DateTime:    2015-04-30 14:43:56
* @Version:     1.0
* @Description: 自下而上的求解背包问题,动态规划
**************************************************/

#include <iostream>
#include <utility>
#include <vector>

using namespace std;

#define MAX(a , b) ((a)>(b)?(a):(b))
/** 背包的容量 */
#define PACKAGE_CAPACITY 50
/** 最多有十个商品 */
#define MAX_GOODS_NUM 10
/** 用来保存商品没有没有选中过 */
bool packageChoose[MAX_GOODS_NUM];
/** 用来存储对应的最大价值 maxValue[i][w]表示后面i个商品装入w容量背包最大价值 */
unsigned maxValue[MAX_GOODS_NUM][PACKAGE_CAPACITY+1];

/**
 * 求得goodslist对应的最大价值
 * @param  goodsList 商品列表
 * @return           最大价值
 */
unsigned zeroOnePackage(std::vector<pair<unsigned , unsigned> > goodsList)
{
    for (int i = 0; i < MAX_GOODS_NUM; ++i)
    {
        packageChoose[i]=false;
    }
    for(int k = goodsList[goodsList.size()-1].second; k <= PACKAGE_CAPACITY  ; ++k)
    {
        maxValue[goodsList.size()-1][k] = goodsList[goodsList.size()-1].first;
    }
    /** 自下而上 */
    for (int i = goodsList.size()-2; i >=0 ; --i)
    {
        /** 就是讲所有的i对应的所有对应容量求出来 , 这样做太浪费了*/
        for(int k = goodsList[i].second; k <= PACKAGE_CAPACITY ; ++k)
        {
            maxValue[i][k] = MAX(maxValue[i+1][k] , maxValue[i+1][k - goodsList[i].second]+goodsList[i].first);
        }
    }
    return maxValue[0][PACKAGE_CAPACITY];
}

void printChoose(unsigned pos , unsigned weight ,     std::vector<pair<unsigned , unsigned> > goodsList)
{
    if(pos >= goodsList.size())
        return;
    if(maxValue[pos][weight] == maxValue[pos+1][weight - goodsList[pos].second]+goodsList[pos].first)
    {
        cout<<"a"<<pos<<"\t";
        printChoose(pos+1 , weight - goodsList[pos].second , goodsList);
    }
    else
    {
        printChoose(pos+1 , weight , goodsList);
    }
}

int main(int argc, char const *argv[])
{
    std::vector<pair<unsigned , unsigned> > goodsList;
    goodsList.push_back(pair<unsigned , unsigned>(60,10));
    goodsList.push_back(pair<unsigned , unsigned>(100,20));
    goodsList.push_back(pair<unsigned , unsigned>(120,30));
    cout<<"max value is : "<<zeroOnePackage(goodsList)<<endl;
    printChoose(0 , PACKAGE_CAPACITY , goodsList);
    while(1);
    return 0;
}

运行以后我们得到结果:

max value is 220

a1 a2

动态规划算法之间的差别

上面两个算法都是动态规划算法,但是两者之间存在细微的差别。

在自上而下的算法中:我们观察到

unsigned temp1 = dealZeroOnePackage(pos+1,weight , goodsList);
unsigned temp2 = dealZeroOnePackage(pos+1, weight - goodsList[pos].second, goodsList)+goodsList[pos].first;

上面的代码是对问题的递归求解,对应的f(i,W)在求解以后就放入到

maxValue[pos][weight]= retValue;               

每一步都只求解对应maxValuepos,weight位置,并不求解maxValue这个二维数组的其他位置


而在自下而上的算法中:我们观察到

有这样一个循环式子:

    for (int i = goodsList.size()-2; i >=0 ; --i)
    {
        /** 就是讲所有的i对应的所有对应容量求出来
        , 这样做太浪费了*/
        for(int k = goodsList[i].second;         k <= PACKAGE_CAPACITY ; ++k)
        {
            maxValue[i][k] = MAX(maxValue[i+1][k] ,            maxValue[i+1][k -            goodsList[i].second]+goodsList[i].first);
        }
    }

在这个循环式子例子都做了写什么呢?

那就是将i对应的这一行差不多所有的maxVlaue数组位置的值都求解出来了,但是我们用到的只有两个。这样做是因为如果要求f(i,W),那么就首先要知道f(i+1,W)和f(i+1,W?wi),但是这里面的W和W?wi是不确定的,我们只有将数组maxValue的i+1这一行的所有值求出来,上层节点才能随意调用。

所及自上而下的算法缺点在于太多次的递归调用,优点就是不用求出所有的全部情况,自下而上的算法缺点在于需要求出所有的情况,优点在于采用迭代而不是递归调用!但是就对于0-1背包问题来说,个人觉得还是自上而下的求法比较适合,因为这里的maxValue数组是由物品个数和背包容量决定的,如果背包容量升级到500,那我每一行岂不是要算五百个数据?,而递归的话只用算出自己需要的数据即可!

时间: 2024-10-28 11:51:05

0-1背包问题与分数背包问题的相关文章

分数背包问题(贪心算法)

#include<iostream> #include<iterator> #include<vector> #include<algorithm> using namespace std; /* *分数背包问题(贪心算法) */ struct goods { double value;//物品的价值 double weight;//物品的重量 double ratio;//物品的性价比 double in;//物品装入背包的重量 int index;//物

01背包问题和完全背包问题

在hihocoder上面的题目中看到的这个问题,总结一下.先看01背包问题. 01背包问题:一个背包总容量为V,现在有N个物品,第i个 物品体积为weight[i],价值为value[i],现在往背包里面装东西,怎么装能使背包的内物品价值最大? 看到这个问题,可能会想到贪心算法,但是贪心其实是不对的.例如最少硬币找零问题,要用动态规划.动态规划思想就是解决子问题并记录子问题的解,这样就不用重复解决子问题了. 动态规划先找出子问题,我们可以这样考虑:在物品比较少,背包容量比较小时怎么解决?用一个数

ACM:动态规划,物品无限的背包问题(完全背包问题)

题目:有n种物品,每种物品都有无限件可用.第i种物品的体积是vi,重量是wi.选一些物品装到一个容量为C的背包中,使得背包内物品在总体积不超过C的前提下重量尽量大. 分析,完全背包问题,相对于上上篇文章的硬币问题,只是由DAG上的无权图变成了这里的DAG上的带权图! 输出最后满足体积不超过背包容量的条件下,背包中的最大重量. 代码: #include <iostream> #include <string> using namespace std; const int MAXN =

动态规划 -- 01背包问题和完全背包问题

动态规划的01背包问题和完全背包问题模板 01背包问题模板: // 01背包问题 #include <stdio.h> #include <algorithm> using namespace std; const int maxn = 100; // 物品的最大件数 const int maxv = 1000; // V的上限 int w[maxn], c[maxn], dp[maxv]; int main() { // 边界 for (int v = 0; v <= V;

1257 背包问题&#160;V3——分数规划

N个物品的体积为W1,W2......Wn(Wi为整数),与之相对应的价值为P1,P2......Pn(Pi为整数),从中选出K件物品(K <= N),使得单位体积的价值最大. Input 第1行:包括2个数N, K(1 <= K <= N <= 50000) 第2 - N + 1行:每行2个数Wi, Pi(1 <= Wi, Pi <= 50000) Output 输出单位体积的价值(用约分后的分数表示). Input示例 3 2 2 2 5 3 2 1 Output示

【算法导论】0-1背包问题

一.0-1背包问题描述: 已知:小偷在店里偷东西,小偷只带了一个最大承重为W的背包,商店里有N件商品,第i件商品的重量是weight[i],价钱是value[i]. 限制:每种商品只有一件,可以选择拿或者不拿,不能分割,不能只拿一件商品的一部分(所以叫做0-1,0即不拿,1则整个拿走,且一种商品有且只有一件可供拿走) 问题:在不超过背包最大承重的情况下,最多能拿走多少钱的商品. 算导上与0-1背包问题对应的是分数背包问题,分数背包问题中的物品是可以取一部分的,就是说可以拆分的,不像0-1背包中,

背包问题:动态规划和贪心算法

1. 动态规划 以下关于动态规划的文字描述来源 动态规划之背包问题(一) 作者:Hawstein 出处:http://hawstein.com/posts/dp-knapsack.html 一切都要从一则故事说起. 话说有一哥们去森林里玩发现了一堆宝石,他数了数,一共有n个. 但他身上能装宝石的就只有一个背包,背包的容量为C.这哥们把n个宝石排成一排并编上号: 0,1,2,-,n-1.第i个宝石对应的体积和价值分别为V[i]和W[i] .排好后这哥们开始思考: 背包总共也就只能装下体积为C的东西

【算法问题】0-1背包问题

0-1背包问题:有一个贼在偷窃一家商店时,发现有n件物品,第i件物品价值vi元,重wi磅,此处vi与wi都是整数.他希望带走的东西越值钱越好,但他的背包中至多只能装下W磅的东西,W为一整数.应该带走哪几样东西?这个问题之所以称为0-1背包,是因为每件物品或被带走:或被留下:小偷不能只带走某个物品的一部分或带走同一物品两次. 在分数(部分)背包问题(fractional knapsack problem)中,场景与上面问题一样,但是窃贼可以带走物品的一部分,而不必做出0-1的二分选择.可以把0-1

[转载学习] 背包问题九讲

背包问题九讲 v1.0 目录 第一讲 01背包问题 第二讲 完全背包问题 第三讲 多重背包问题 第四讲 混合三种背包问题 第五讲 二维费用的背包问题 第六讲 分组的背包问题 第七讲 有依赖的背包问题 第八讲 泛化物品 第九讲 背包问题问法的变化 附:USACO中的背包问题 前言 本篇文章是我(dd_engi)正在进行中的一个雄心勃勃的写作计划的一部分,这个计划的内容是写作一份较为完善的NOIP难度的动态规划总结,名为<解动态规划题的基本思考方式>.现在你看到的是这个写作计划最先发布的一部分.