01背包问题

问题描述

0-1背包问题:给定n种物品和一-背包。物品i的重量是wi, 其价值为Vi, 背包的容量为C。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大?在选择装入背包的物品时,对每种物品i只有两种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。因此,该问题称为0-1背包问题。
在这里插入图片描述

最优子结构分析

在这里插入图片描述
在这里插入图片描述

确定递推方程

对于这个整数规划问题,我们用m(i,j)来表述可选择物品为i,i+1,i+2…n在背包容量为j,时的最优值。那么我们可以得出如下状态转移方程:

  • m(i,j)=max{m(i+1,j),m(i+1,j-wi)+vi},j>=wi
  • m(i,j)=m(i+1,j),j<wi
  • 最优解即为m[1][c]
    以下是一个实例:
    在这里插入图片描述

算法实现

实现1:
对于这个递推方程我们最简单的实现是定义一个n+1*n+1的数组,将其初始化为0,然后根据递推式直接填充,易于实现,预处理十分简单:

int dpFunc1(int m[][maxn],int n,int c)
{
    for(int i=0;i<n+1;i++) m[i][0]=0;//初始化第0列
    for(int j=0;j<c+1;j++) m[0][j]=0;//初始化第0行
    for(int i=n;i>=1;i--)//序号
    {
        for(int j=1;j<=c;j++)//背包剩余容量
        {
                if(j>=w[i]) m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
                else m[i][j]=m[i+1][j];
        }
    }
    return m[1][c];//返回最优解
}                                                                                                                                                                                                                                                                                                           

实现2:knapsack算法
这个算法相较于实现1,有效避免了不必要及比较过程,当j<w[i]的时候直接填充m[i+1][j]而不需要比较,另外由于最后一行的前c-1个数据并没有使用,所以无需计算。

int knapsack(int m[][maxn],int n,int c)
{
    int jMax=min(w[n],c);
    //预处理第一行,j小于w[n]填充物为0,否则填充为v[n]
    for(int j=0;j<jMax;j++) m[n][j]=0;
    for(int j=jMax;j<c;j++) m[n][j]=v[n];
    for(int i=n-1;i>1;i--)//最后一行不计算,因为有一部分填充是无效的
    {
        jMax=min(w[i],c);
        for(int j=0;j<jMax;j++)
            m[i][j]=m[i+1][j];//不选
        for(int j=jMax;j<c+1;j++)
            m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
    }
    m[1][c]=max(m[2][c],m[2][c-w[1]]+v[1]);//处理最后一行的最后一个数据
    return m[1][c];
}                                                                                                                                                                                                                                                                                                                   

求解最优解的选取方式traceBack回溯

形成以上的一组表格之后我们可以很容易得到最优解m[1][c],但是如何求解选取方式呢?我们仍然可以根据递推式回溯:

  • 如果m[i][c]==m[i+1][c],说明i号物品并没有被选取,i++
  • 如果m[i][c]!=m[i+1][c],说明i号物品被选取,i++,c-wi
  • 当i=n时,如果m[n][c]!=0说明n号物品被选取,否则未被选取
void traceBack(int m[maxn][maxn],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;
}	

总算法实现

#include <iostream>
using namespace std;
const int maxn=1005;
int m[maxn][maxn]={0};
int w[maxn]={0};
int v[maxn]={0};
int dpFunc1(int m[][maxn],int n,int c)
{
    for(int i=0;i<n+1;i++) m[i][0]=0;//初始化第0列
    for(int j=0;j<c+1;j++) m[0][j]=0;//初始化第0行
    for(int i=n;i>=1;i--)//序号
    {
        for(int j=1;j<=c;j++)//背包剩余容量
        {
                if(j>=w[i]) m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
                else m[i][j]=m[i+1][j];
        }
    }
    return m[1][c];//返回最优解
}
int knapsack(int m[][maxn],int n,int c)
{
    int jMax=min(w[n],c);
    //预处理第一行,j小于w[n]填充物为0,否则填充为v[n]
    for(int j=0;j<jMax;j++) m[n][j]=0;
    for(int j=jMax;j<c;j++) m[n][j]=v[n];
    for(int i=n-1;i>1;i--)//最后一行不计算,因为有一部分填充是无效的
    {
        jMax=min(w[i],c);
        for(int j=0;j<jMax;j++)
            m[i][j]=m[i+1][j];//不选
        for(int j=jMax;j<c+1;j++)
            m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
    }
    m[1][c]=max(m[2][c],m[2][c-w[1]]+v[1]);//处理最后一行的最后一个数据
    return m[1][c];
}
void traceBack(int m[maxn][maxn],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;
}
int main()
{
    int n,c,x[maxn]={0};
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    {
        cin>>w[i]>>v[i];
    }
    int res=dpFunc1(m,n,c);//函数调用
    cout<<res<<endl;
    res=knapsack(m,n,c);
    cout<<res<<endl;
    traceBack(m,w,c,n,x);
    for(int i=1;i<n+1;i++) cout<<i<<' '<<x[i]<<endl;
    system("pause");
    return 0;
} 
/*
5 7
2 4
3 4
1 2
2 7
4 8
*/                                                                                                                                                                                                                                                                                                                    

算法复杂度分析

时间复杂度:T(n)=O(n^2)
空间复杂度:S(n)=O(n^2)

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值