分枝限界法求解0/1背包问题

问题描述

有n个重量分别为{w1,w2,…,wn}的物品,它们的价值分别为{v1,v2,…,vn},给定一个容量为W的背包。
设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且重量和为W具有最大的价值。
假设一个0/1背包问题是,n=3,重量为w=(16,15,15),价值为v=(45,25,25),背包限重为W=30,解向量为x=(x1,x2,x3)。
在这里插入图片描述

采用队列式分枝限界法求解

在这里插入图片描述
采用STL的queue< NodeType >容器qu作为队列,队列中的结点类型声明如下:

struct NodeType		//队列中的结点类型
{  int no;			//结点编号,从1开始
   int i;			//当前结点在搜索空间中的层次
   int w;			//当前结点的总重量
   int v;			//当前结点的总价值
   int x[MAXN];		//当前结点包含的解向量
   double ub;			//上界
};

现在设计限界函数,为了简便,设根结点为第0层,然后各层依次递增,显然i=n时表示是叶子结点层。
由于该问题是求装入背包的最大价值,属求最大值问题,采用上界设计方式。
对于第i层的某个结点e,用e.w表示结点e时已装入的总重量,用e.v表示已装入的总价值:
如果所有剩余的物品都能装入背包,那么价值的上界e.ub=e.v+ (v[i+1]+…+v[n])
如果所有剩余的物品不能全部装入背包,那么价值的上界e.ub=e.v+ (v[i+1]+…+v[k])+(物品k+1装入的部分重量)×物品k+1的单位价值

代码

int n=3,W=30;
int w[]={0,16,15,15};
int v[]={0,45,25,25};
int maxv=-9999;
int bestx[MAXN];
int total=1;

struct NodeType
{
    int no;//节点编号,从1开始
    int i;//当前节点在搜索空间中的层次
    int w;//当前节点的总重量
    int v;//当前节点的总价值
    int x[MAXN];//当前节点包含的解向量
    double ub;//上界
};

void bound(NodeType &e)//计算分枝节点e的上界
{
    int i=e.i+1;//考虑余下的物品
    int sumw=e.w;
    double sumv=e.v;

    while((sumw+w[i]<=W)&&i<=n)
    {
        sumw+=w[i];
        sumv+=v[i];
        i++;
    }

    if(i<=n)//物品只能部分装入
        e.ub=sumv+(W-sumw)*v[i]/w[i];
    else//可以全部装入
        e.ub=sumv;
}

void EnQueue(NodeType e,queue<NodeType> &qu)
{
    if(e.i==n)//到达叶子节点
    {
        if(e.v>maxv)
        {
            maxv=e.v;
            for(int j=1;j<=n;j++)
                bestx[j]=e.x[j];
        }
    }
    else
    {
        qu.push(e);
    }
    
} 

void bfs()
{
    int j;
    NodeType e,e1,e2;
    queue<NodeType> qu;

    //根节点初始化
    e.i=0;
    e.w=0;
    e.v=0;
    e.no=total++;
    for(j=1;j<=n;j++)
        e.x[j]=0;
    bound(e);
    qu.push(e);

    while (!qu.empty())
    {
        e=qu.front();
        qu.pop();

        if(e.w+w[e.i+1]<=W)//检查左孩子节点
        {
            e1.no=total++;
            e1.i=e.i+1;
            e1.w=e.w+w[e1.i];
            e1.v=e.v+v[e1.i];
            for(j=1;j<=n;j++)
                e1.x[j]=e.x[j];
            e1.x[e1.i]=1;//选择物品
            bound(e1);
            EnQueue(e1,qu);
        }

        e2.no=total++;
        e2.i=e.i+1;
        e2.w=e.w;
        e2.v=e.v;
        for(j=1;j<=n;j++)
            e2.x[j]=e.x[j];
        e2.x[e2.i]=0;//不选择物品
        bound(e2);
        if(e2.ub>maxv)//右剪枝
            EnQueue(e2,qu);
    }    
}

采用优先队列式分枝限界法求解

采用优先队列式分枝限界法求解就是将一般的队列改为优先队列,但必须设计限界函数,因为优先级是以限界函数值为基础的。
限界函数的设计方法与前面的相同。这里用大根堆表示活结点表,取优先级为活结点所获得的价值。

struct NodeType		//队列中的结点类型
{  int no;			//结点编号
   int i;			//当前结点在搜索空间中的层次
   int w;			//当前结点的总重量
   int v;			//当前结点的总价值
   int x[MAXN];		//当前结点包含的解向量
   double ub;			//上界
   bool operator<(const NodeType &s) const	//重载<关系函数
   {
      return ub<s.ub;		//ub越大越优先出队
   }
};

代码

int n=3,W=30;
int w[]={0,16,15,15};
int v[]={0,45,25,25};
int maxv=-9999;
int bestx[MAXN];
int total=1;

struct NodeType
{
    int no;//节点编号,从1开始
    int i;//当前节点在搜索空间中的层次
    int w;//当前节点的总重量
    int v;//当前节点的总价值
    int x[MAXN];//当前节点包含的解向量
    double ub;//上界

    bool operator<(const NodeType &s) const
    {
        return ub<s.ub;//ub越大越优先出队
    }
};

void bound(NodeType &e)//计算分枝节点e的上界
{
    int i=e.i+1;//考虑余下的物品
    int sumw=e.w;
    double sumv=e.v;

    while((sumw+w[i]<=W)&&i<=n)
    {
        sumw+=w[i];
        sumv+=v[i];
        i++;
    }

    if(i<=n)//物品只能部分装入
        e.ub=sumv+(W-sumw)*v[i]/w[i];
    else//可以全部装入
        e.ub=sumv;
}

void EnQueue(NodeType e,queue<NodeType> &qu)
{
    if(e.i==n)//到达叶子节点
    {
        if(e.v>maxv)
        {
            maxv=e.v;
            for(int j=1;j<=n;j++)
                bestx[j]=e.x[j];
        }
    }
    else
    {
        qu.push(e);
    }
    
} 

void bfs()
{
    int j;
    NodeType e,e1,e2;
    priority_queue<NodeType> qu;

    //根节点初始化
    e.i=0;
    e.w=0;
    e.v=0;
    e.no=total++;
    for(j=1;j<=n;j++)
        e.x[j]=0;
    bound(e);
    qu.push(e);

    while (!qu.empty())
    {
        e=qu.front();
        qu.pop();

        if(e.w+w[e.i+1]<=W)//检查左孩子节点
        {
            e1.no=total++;
            e1.i=e.i+1;
            e1.w=e.w+w[e1.i];
            e1.v=e.v+v[e1.i];
            for(j=1;j<=n;j++)
                e1.x[j]=e.x[j];
            e1.x[e1.i]=1;//选择物品
            bound(e1);
            EnQueue(e1,qu);
        }

        e2.no=total++;
        e2.i=e.i+1;
        e2.w=e.w;
        e2.v=e.v;
        for(j=1;j<=n;j++)
            e2.x[j]=e.x[j];
        e2.x[e2.i]=0;//不选择物品
        bound(e2);
        if(e2.ub>maxv)//右剪枝
            EnQueue(e2,qu);
    }    
}

算法分析

无论采用队列式分枝限界法还是优先队列式分枝限界法求解0/1背包问题,最坏情况下要搜索整个解空间树,所以最坏时间和空间复杂度均为O(2n),其中n为物品个数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值