算法分析与设计——背包问题

给定n种物品和一背包。物品i的重量是wi>0,其价值为vi>0,背包的容量为c。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? (要求使用回溯法)
在这里插入图片描述
步骤:

  1. 输入物品数量n;
  2. 依次对物品输入重量wi和价值vi;
  3. 将物品按照其单位重量价值从大到小排序;
  4. 构造解的子集树。对于每一个物品i,对于该物品只有选与不选2个决策,总共有n个物品,顺序依次考虑每个物品,这样就形成了一棵解空间树。基本思想就是遍历这棵树,以枚举所有情况,最后进行判断,如果重量不超过背包容量,且价值最大的话,该方案就是最后的答案。
  5. 在递归函数Backtrack中,当i>n时,算法搜索至叶子结点,得到一个新的物品装包方案。此时算法适时更新当前的最优价值。当i<n时,当前扩展结点位于排列树的第(i-1)层,此时算法选择下一个要安排的物品,以深度优先方式递归的对相应的子树进行搜索,对不满足上界约束的结点,则剪去相应的子树。
  6. 在搜索状态空间树时,只要左子节点是可一个可行结点,搜索就进入其左子树。对于右子树时,先计算上界函数,以判断是否将其剪枝。

实验原理:

回溯法是一种非常有效的方法,有“通用的解题法”之称。它有点像穷举法,但是更带有跳跃性和系统性,他可以系统性的搜索一个问题的所有的解和任一解。回溯法采用的是深度优先策略。

回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。

实验程序:
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>
#include <stdlib.h>
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;
}

实验测试:
在这里插入图片描述
复杂度分析:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值