第十六章 贪心算法——0/1背包问题

 1、问题描述

     给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大?

     形式化描述:给定c >0, wi >0, vi >0 , 1≤i≤n.要求找一n元向量(x1,x2,…,xn,), xi∈{0,1}, ∋ ∑ wi xi≤c,且∑ vi xi达最大.即一个特殊的整数规划问题。

       2、最优性原理

     设(y1,y2,…,yn)是 (3.4.1)的一个最优解.则(y2,…,yn)是下面相应子问题的一个最优解:

     证明:使用反证法。若不然,设(z2,z3,…,zn)是上述子问题的一个最优解,而(y2,y3,…,yn)不是它的最优解。显然有
                                    ∑vizi > ∑viyi   (i=2,…,n)
     且                           w1y1+ ∑wizi<= c
     因此                       v1y1+ ∑vizi (i=2,…,n) > ∑ viyi, (i=1,…,n) 
     说明(y1,z2, z3,…,zn)是(3.4.1)0-1背包问题的一个更优解,导出(y1,y2,…,yn)不是背包问题的最优解,矛盾。

       3、递推关系

    设所给0-1背包问题的子问题

     

     的最优值为m(i,j),即m(i,j)是背包容量为j,可选择物品为i,i+1,…,n时0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式:

     注:(3.4.3)式此时背包容量为j,可选择物品为i。此时在对xi作出决策之后,问题处于两种状态之一:
    (1)背包剩余容量是j,没产生任何效益;
    (2)剩余容量j-wi,效益值增长了vi ;
     使用递归C++代码如下:

  

#include<iostream>
using namespace std;

const int N=3;
const int W=50;
int weights[N+1]={0,10,20,30};
int values[N+1]={0,60,100,120};
int V[N+1][W+1]={0};

int knapsack(int i,int j)
{
    int value;
    if(V[i][j]<0)
    {
        if(j<weights[i])
        {
            value=knapsack(i-1,j);
        }
        else
        {
            value=max(knapsack(i-1,j),values[i]+knapsack(i-1,j-weights[i]));
        }
        V[i][j]=value;
    }
    return V[i][j];
}

int main()
{
    int i,j;
    for(i=1;i<=N;i++)
        for(j=1;j<=W;j++)
            V[i][j]=-1;
    cout<<knapsack(3,50)<<endl;
    cout<<endl;
}

不使用递归的C++代码:简单一点的修改http://www.cppblog.com/Geek/archive/2009/12/02/102393.html

//3d10-1 动态规划 背包问题
#include <iostream>
using namespace std;

const int N = 4;

void Knapsack(int v[],int w[],int c,int n,int m[][10]);
void Traceback(int m[][10],int w[],int c,int n,int x[]);

int main()
{
    int c=8;
    int v[]={0,2,1,4,3},w[]={0,1,4,2,3};//下标从1开始
    int x[N+1];
    int m[10][10];

    cout<<"待装物品重量分别为:"<<endl;
    for(int i=1; i<=N; i++)
    {
        cout<<w[i]<<" ";
    }
    cout<<endl;

    cout<<"待装物品价值分别为:"<<endl;
    for(int i=1; i<=N; i++)
    {
        cout<<v[i]<<" ";
    }
    cout<<endl;

    Knapsack(v,w,c,N,m);

    cout<<"背包能装的最大价值为:"<<m[1][c]<<endl;

    Traceback(m,w,c,N,x);
    cout<<"背包装下的物品编号为:"<<endl;
    for(int i=1; i<=N; i++)
    {
        if(x[i]==1)
        {
            cout<<i<<" ";
        }
    }
    cout<<endl;

    return 0;
}

void Knapsack(int v[],int w[],int c,int n,int m[][10])
{
    int jMax = min(w[n]-1,c);//背包剩余容量上限 范围[0~w[n]-1]
    for(int j=0; j<=jMax;j++)
    {
        m[n][j]=0;
    }

    for(int j=w[n]; j<=c; j++)//限制范围[w[n]~c]
    {
        m[n][j] = v[n];
    }

    for(int i=n-1; i>1; i--)
    {
        jMax = min(w[i]-1,c);
        for(int j=0; j<=jMax; j++)//背包不同剩余容量j<=jMax<c
        {
            m[i][j] = m[i+1][j];//没产生任何效益
        }

        for(int j=w[i]; j<=c; j++) //背包不同剩余容量j-wi >c
        {
            m[i][j] = max(m[i+1][j],m[i+1][j-w[i]]+v[i]);//效益值增长vi
        }
    }
    m[1][c] = m[2][c];
    if(c>=w[1])
    {
        m[1][c] = max(m[1][c],m[2][c-w[1]]+v[1]);
    }
}

//x[]数组存储对应物品0-1向量,0不装入背包,1表示装入背包
void Traceback(int m[][10],int w[],int c,int n,int x[])
{
    for(int i=1; i<n; i++)
    {
        if(m[i][c] == m[i+1][c])
        {
            x[i]=0;
        }
        else
        {
            x[i]=1;
            c-=w[i];
        }
    }
    x[n]=(m[n][c])?1:0;
}

运行结果:

 

算法执行过程对m[][]填表及Traceback回溯过程如图所示:

      从m(i,j)的递归式容易看出,算法Knapsack需要O(nc)计算时间; Traceback需O(n)计算时间;算法总体需要O(nc)计算时间。当背包容量c很大时,算法需要的计算时间较多。例如,当c>2^n时,算法需要Ω(n2^n)计算时间。

时间: 2024-09-25 04:07:21

第十六章 贪心算法——0/1背包问题的相关文章

python 教程 第十六章、 正则表达式

第十六章. 正则表达式 1)    匹配多个表达式 记号  re1|re2 说明  匹配正则表达式re1或re2 举例  foo|bar  匹配  foo, bar 记号  {N} 说明  匹配前面出现的正则表达式N 举例  [0-9]{3}  匹配  2)    匹配单个/多个/范围内字符 记号  . 说明  匹配任何字符(换行符除外) 举例  b.b  匹配  b和b中间有一个任意字符bab, bcb, bbb 举例  .. (匹配任何两个字符)  匹配  xx, ab 记号  [-] 说明

第十六章——处理锁、阻塞和死锁(1)——确定长时间运行的事务

原文:第十六章--处理锁.阻塞和死锁(1)--确定长时间运行的事务 前言: 事务是OLTP系统中的主要部分.它管理数据一致性和数据并发问题,当多个资源同时被读取或者修改相同数据时,SQLServer会通过锁定机制来确保数据库中的数据总是处于一个有效状态.在SQLServer中,锁管理器是负责实现这些锁机制.SQLServer对于不同的资源类型提供不同的锁类型,如数据库.文件.对象.表.区.页和键. 当你使用事务时,依然会遇到由事务引起的问题,这些通常是由于锁.阻塞和死锁引起的. 本系列将讲解这三

第十六章——处理锁、阻塞和死锁(3)——使用SQLServer Profiler侦测死锁

原文:第十六章--处理锁.阻塞和死锁(3)--使用SQLServer Profiler侦测死锁 前言: 作为DBA,可能经常会遇到有同事或者客户反映经常发生死锁,影响了系统的使用.此时,你需要尽快侦测和处理这类问题. 死锁是当两个或者以上的事务互相阻塞引起的.在这种情况下两个事务会无限期地等待对方释放资源以便操作.下面是死锁的示意图: 本文将使用SQLServer Profiler来跟踪死锁.   准备工作: 为了侦测死锁,我们需要先模拟死锁.本例将使用两个不同的会话创建两个事务.   步骤:

第十六章——处理锁、阻塞和死锁(2)——侦测阻塞和阻塞查询

原文:第十六章--处理锁.阻塞和死锁(2)--侦测阻塞和阻塞查询 前言: 如果一个事务正在等待一些给其他事务锁定的资源.这个事务就被成为"被阻塞的事务".反过来,引起阻塞的事务,也就是锁定资源并造成其他事务等待的事务叫做"正在阻塞的事务". 长时间运行事务会阻塞其他事务和查询,使他们等待长时间.在繁重的系统中,很多时候我们会遇到阻塞问题,如果一个事务因为阻塞未完成.会造成一些列的等待链. 本文将介绍如何发现并马上解决这方面的问题.   准备工作: 本例依旧使用SQL

《文明之光 第二册》一一第十六章 两个人的竞赛—— 苏美航天发展的历程

第十六章 两个人的竞赛-- 苏美航天发展的历程 文明之光 第二册军事的需求往往会推动科学技术的发展,而这些技术民用化之后又促进了人类文明的进步.美苏太空争霸导致了太空技术的飞速发展,而在这背后,很大程度上是两个天才的默默竞争. 1944年6月6日,军事史上最著名的D 日(D-Day).这一天,艾森豪威尔将军率领的盟军在法国诺曼底成功登陆,之后盟军迅速向纳粹占领的法国纵深推进.纳粹德国离最终的失败已为期不远了,在战争中饱受德国空军袭..

Flash基础理论课 第十六章 3D线条与填充Ⅰ

返回"Flash基础理论课 - 目录" 第十五章我们介绍了3D,但只是将物体置于3D空间中,设置大小与位置.物体实际上还是2D的.这就像老的3D游戏中,我们可以绕着某个物体或人物走,这些对象会转过来面对我们.这些物体或人物并不是真正的会转过来 -- 只是看上去是这样的,因为它们都是2D 物体,那是我们看到它唯一的一个视角. 本章,我们将真正地在 Flash中创建3D 模型.具体说来有创建并使用3D 点,线条,填充以及立体图形.学习完本章,大家就可以任意在三维空间中创建各种形状,并对它们

经典算法题每日演练——第十六题 Kruskal算法

     这篇我们看看第二种生成树的Kruskal算法,这个算法的魅力在于我们可以打一下算法和数据结构的组合拳,很有意思的. 一:思想     若存在M={0,1,2,3,4,5}这样6个节点,我们知道Prim算法构建生成树是从"顶点"这个角度来思考的,然后采用"贪心思想" 来一步步扩大化,最后形成整体最优解,而Kruskal算法有点意思,它是站在"边"这个角度在思考的,首先我有两个集合. 1. 顶点集合(vertexs):      比如M集合

WF从入门到精通(第十六章):声明式工作流

学习完本章,你将掌握: 1.理解过程式(imperative)工作流模型和声明式(declarative)工作流模型之间的主要区别 2.创建声明式工作流 3.使用XAML XML词汇来创建工作流 4.调入基于XAML的工作流并执行 许多开发者或许并不知道WF既能用基于过程化的定义来执行工作流(使用工作流视图设计器)也能用基于声明式的定义来执行工作流(工作流使用XML来进行定义). 每一种风格都有优点.当你使用我们贯穿本书已使用过的技术来创建你的工作流应用程序的时候,工作流模型实际上是被编译进了一

Flash基础理论课 第十六章 3D线条与填充 Ⅲ

返回"Flash基础理论课 - 目录" 建立其它形状的模型 恭喜各位!您已经掌握了旋转立方体.现在我们可以去建立所有种类的形状了.只要先将它们绘制在方格纸上,标出点和三角形,再将放入数组即可.这张图可以帮助我们用几种视角绘出物体,旋转后可以看到每个面以及在三角形上标出的点.本节提供了一些其它的图形作为起点. 金字塔形 下面是3D 金字塔形的代码(可以在Pyramid.as中找到).首先是点: points[0] = new Point3D( 0, -200, 0); points[1]