0-1背包问题的分枝—限界算法

摘要:
分枝定界法通常以广度优先或最小成本优先的方式搜索问题的解空间树。当搜索问题的解空间树时,分支定界方法的每个活动节点只有一次机会成为扩展节点。

 

1.分枝—限界法的基本原理

分枝—限界算法类似于回溯法,也是一种在问题的解空间树上搜索问题解的算法。但两者求解方法有两点不同:第一,回溯法只通过约束条件剪去非可行解,而分枝—限界法不仅通过约束条件,而且通过目标函数的限界来减少无效搜索,也就是剪掉了某些不包含最优解的可行解;第二,在解空间树上,回溯法以深度优先搜索,而分枝—限界法则以广度优先或最小耗费优先的方式搜索。分枝—限界的搜索策略是,在扩展节点处,首先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展结点。为了有效地选择下一扩展结点,以加速搜索进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值从当前活结点表中选择一个最有利的结点做为扩展,使搜索朝着解空间树上最优解的分支推进,以便尽快找出一个最优解。

分枝—限界法常以广度优先或以最小耗费优先的方式搜索问题的解空间树(问题的解空间树是表示问题皆空间的一颗有序树,常见的有子集树和排序树)。在搜索问题的解空间树时,分枝—限界法的每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或非最优解的儿子结点将被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表取出下一结点成为当前扩展结点,并重复上述扩展过程,直到找到最优解或活结点表为空时停止。

2. 0-1背包问题的分枝—限界算法的数据结构

template<class Typew,class Typep>

class HeapNode

{

    friend Knap<Typew,Typep>;

    public:

        operator Typep() const

        {

            return uprofit;

        }

    private:

        Typep uprofit,        //节点的价值上界

             profit;        //节点所相应的价值

        Typew weight;        //节点所相应的重量

        int level;            //活节点在子集树中所处的层序号

        bbnode *ptr;        //指向活节点在子集中相应节点的指针

};

3. 限界函数:

Typep Knap<Typew,Typep>::Bound(int i)//计算节点所相应价值的上界

{

    Typew cleft = c-cw;    //剩余容量高

    Typep b = cp;        //价值上界

    //以物品单位重量价值递减序装填剩余容量

    while(i<=n && w[i]<=cleft)

    {

        cleft -= w[i];

        b += p[i];

        i++;

    }

4.算法具体实现主要代码如下:

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

#include "stdafx.h"

#include "MaxHeap.h"

#include <iostream>

using namespace std;

class Object

{

    template<class Typew,class Typep>

    friend Typep Knapsack(Typep p[],Typew w[],Typew c,int n, int bestx[]);

    public:

        int operator <= (Object a) const

        {

            return d>=a.d;

        }

    private:

        int ID;

        float d;//单位重量价值

};

 

template<class Typew,class Typep> class Knap;

 

class bbnode

{

    friend Knap<int,int>;

    template<class Typew,class Typep>

    friend Typep Knapsack(Typep p[],Typew w[],Typew c,int n, int bestx[]);

    private:

        bbnode * parent;    //指向父节点的指针

        bool LChild;        //左儿子节点标识

};

 

template<class Typew,class Typep>

class HeapNode

{

    friend Knap<Typew,Typep>;

    public:

        operator Typep() const

        {

            return uprofit;

        }

    private:

        Typep uprofit,        //节点的价值上界

             profit;        //节点所相应的价值

        Typew weight;        //节点所相应的重量

        int level;            //活节点在子集树中所处的层序号

        bbnode *ptr;        //指向活节点在子集中相应节点的指针

};

 

template<class Typew,class Typep>

class Knap

{

    template<class Typew,class Typep>

    friend Typep Knapsack(Typep p[],Typew w[],Typew c,int n, int bestx[]);

    public:

        Typep MaxKnapsack();

private:

        MaxHeap<HeapNode<Typep,Typew>> *H;

Typep Bound(int i);

        void AddLiveNode(Typep up,Typep cp,Typew cw,bool ch,int lev);

 

        bbnode *E;    //指向扩展节点的指针

Typew c; //背包容量

int n; //物品数

 

Typew *w; //物品重量数组

Typep *p; //物品价值数组

Typew cw; //当前重量

 

Typep cp; //当前价值

int *bestx; //最优解

};

 

template <class Type>

inline void Swap(Type &a,Type &b);

 

template<class Type>

void BubbleSort(Type a[],int n);

 

int main()

{

    int n = 3;//物品数

int c = 30;//背包容量

int p[] = {0,45,25,25};//物品价值 下标从1开始

int w[] = {0,16,15,15};//物品重量 下标从1开始

    int bestx[4];//最优解

 

cout<<"背包容量为:"<<c<<endl;

cout<<"物品重量和价值分别为:"<<endl;

 

for(int i=1; i<=n; i++)

{

cout<<"("<<w[i]<<","<<p[i]<<") ";

}

cout<<endl;

 

cout<<"背包能装下的最大价值为:"<<Knapsack(p,w,c,n,bestx)<<endl;

    cout<<"此背包问题最优解为:"<<endl;

    for(int i=1; i<=n; i++)

    {

        cout<<bestx[i]<<" ";

    }

    cout<<endl;

return 0;

}

 

template<class Typew,class Typep>

Typep Knap<Typew,Typep>::Bound(int i)//计算节点所相应价值的上界

{

    Typew cleft = c-cw;    //剩余容量高

    Typep b = cp;        //价值上界

    //以物品单位重量价值递减序装填剩余容量

    while(i<=n && w[i]<=cleft)

    {

        cleft -= w[i];

        b += p[i];

        i++;

    }

 

    //装填剩余容量装满背包

    if(i<=n)

    {

        b += p[i]/w[i]*cleft;

    }

 

    return b;

}

 

//将一个活节点插入到子集树和优先队列中

template<class Typew,class Typep>

void Knap<Typew,Typep>::AddLiveNode(Typep up,Typep cp,Typew cw,bool ch,int lev)

{

    bbnode *b = new bbnode;

    b->parent = E;

    b->LChild = ch;

 

    HeapNode<Typep,Typew> N;

    N.uprofit = up;

    N.profit = cp;

    N.weight = cw;

    N.level = lev;

    N.ptr = b;

 

    H->Insert(N);

}

 

//优先队列式分支限界法,返回最大价值,bestx返回最优解

template<class Typew,class Typep>

Typep Knap<Typew,Typep>::MaxKnapsack()

{

    H = new MaxHeap<HeapNode<Typep,Typew>>(1000);

 

    //为bestx分配存储空间

    bestx = new int[n+1];

 

    //初始化

    int i = 1;

    E = 0;

    cw = cp = 0;            

    Typep bestp = 0;//当前最优值

    Typep up = Bound(1);    //价值上界

 

    //搜索子集空间树

    while(i!=n+1)

    {

        //检查当前扩展节点的左儿子节点

        Typew wt = cw + w[i];

        if(wt <= c)//左儿子节点为可行节点

        {

            if(cp+p[i]>bestp)

            {

                bestp = cp + p[i];

            }

            AddLiveNode(up,cp+p[i],cw+w[i],true,i+1);            

        }

 

        up = Bound(i+1);

        //检查当前扩展节点的右儿子节点

        if(up>=bestp)//右子树可能含有最优解

        {

            AddLiveNode(up,cp,cw,false,i+1);

        }

 

        //取下一扩展节点

        HeapNode<Typep,Typew> N;

        H->DeleteMax(N);

        E = N.ptr;

        cw = N.weight;

        cp = N.profit;

        up = N.uprofit;

        i = N.level;

    }

 

    //构造当前最优解

    for(int j=n; j>0; j--)

    {

        bestx[j] = E->LChild;

        E = E->parent;

    }

    return cp;

}

 

//返回最大价值,bestx返回最优解

template<class Typew,class Typep>

Typep Knapsack(Typep p[],Typew w[],Typew c,int n, int bestx[])

{

    //初始化

    Typew W = 0;    //装包物品重量

    Typep P = 0;    //装包物品价值

    

    //定义依单位重量价值排序的物品数组

    Object *Q = new Object[n];

    for(int i=1; i<=n; i++)

    {

        //单位重量价值数组

        Q[i-1].ID = i;

        Q[i-1].d = 1.0*p[i]/w[i];

        P += p[i];

        W += w[i];

    }

 

    if(W<=c)

    {

        return P;//所有物品装包

    }

 

    //依单位价值重量排序

    BubbleSort(Q,n);

 

    //创建类Knap的数据成员

    Knap<Typew,Typep> K;

    K.p = new Typep[n+1];

    K.w = new Typew[n+1];

 

    for(int i=1; i<=n; i++)

    {

        K.p[i] = p[Q[i-1].ID];

        K.w[i] = w[Q[i-1].ID];

    }

 

    K.cp = 0;

    K.cw = 0;

    K.c = c;

    K.n = n;

 

    //调用MaxKnapsack求问题的最优解

    Typep bestp = K.MaxKnapsack();

    for(int j=1; j<=n; j++)

    {

        bestx[Q[j-1].ID] = K.bestx[j];

    }

 

    delete Q;

    delete []K.w;

    delete []K.p;

    delete []K.bestx;

    return bestp;    

}

template<class Type>

void BubbleSort(Type a[],int n)

{

//记录一次遍历中是否有元素的交换

bool exchange;

for(int i=0; i<n-1;i++)

{

exchange = false ;

for(int j=i+1; j<=n-1; j++)

{

if(a[j]<=a[j-1])

{

Swap(a[j],a[j-1]);

exchange = true;

}

}

//如果这次遍历没有元素的交换,那么排序结束

if(false == exchange)

{

break ;

}

}

}

 

template <class Type>

inline void Swap(Type &a,Type &b)

{

Type temp = a;

a = b;

b = temp;

}

编译并运行程序。

5. 0-1背包问题的分枝—限界算法的搜索过程与解空间树

当n=3时,w={16,15,15}, p={45,25,25}, c=30。优先队列式分支限界法:处理法则:价值大者优先。{}—>{A}—>{B,C}—>{C,D,E}—>{C,E}—>{C,J,K}—>{C}—>{F,G}—>{G,L,M}—>{G,M}—>{G}—>{N,O}—>{O}—>{}

0-1背包问题的分枝—限界算法第1张

6.算法的时间复杂性和空间复杂性

0-1背包问题的分枝—限界算法第2张

0-1背包问题不同算法时间复杂性和空间复杂性的比较

0-1背包问题的分枝—限界算法的时间复杂度为:O(n*2n),空间复杂度为:O(nm),

0-1背包问题的回溯法时间复杂度为:O(n*2n),与分枝—限界算法相同,而空间复杂度与分枝—限界算法不同,为:θ(n)。

测试数据:

背包容量为30

物品重量和价值分别为:

16,45

15,25

15,25

程序运行结果如下:

0-1背包问题的分枝—限界算法第3张

 

 

为了方便只取了一组简单的测试数据,可能会对实验结果造成影响,后面还要取更多的数据进行测试。

免责声明:文章转载自《0-1背包问题的分枝—限界算法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇iOS 项目调试C++ Opencv 傅里叶变换的代码实现及关键函数详解下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

从零开始学区块链(4)

转自:区块链大师 1. 传统分布式一致性算法和区块链共识过程的异同点 相同点: Append only(只能增加) 强调序列化 少数服从多数原则 分离覆盖的问题:即长链覆盖短链区块,多节点覆盖少数节点日志 不同点: 传统分布式一致性算法大多不考虑拜占庭容错(Byzanetine Paxos除外),即假设所有节点只发生宕机、网络故障等非人为问题,并不考...

常见的加密方式总结

对称加密 DES DES加密算法是一种分组密码,以64位为分组对数据加密,它的密钥长度是56位,加密解密用同一算法,加密速度快,但是容易破解安全性低。 3DES(Triple DES) 是基于DES的对称算法,对一块数据用三个不同的密钥进行三次加密,强度更高,加强版DES。 (DES算法比较简单,容易破解已不建议使用) AES(微信用的就是这种加密方式)...

盘点一下数据平滑算法

本文参考来自于:http://blog.csdn.net/wwjiang_ustc/article/details/50732211   在自然语言处理中,经常要计算单词序列(句子)出现的概率估计。我们知道,算法在训练时,语料库不可能包含所有可能出现的序列。     因此,为了防止对训练样本中未出现的新序列概率估计值为零,人们发明了好多改善估计新序列出现概...

OpenCV学习(13) 细化算法(1)

程序编码参考经典的细化或者骨架算法文章: T. Y. Zhang and C. Y. Suen, “A fast parallel algorithm for thinning digital patterns,” Comm. ACM, vol. 27, no. 3, pp. 236-239, 1984. 它的原理也很简单:       我们对一副二值图像...

机器视觉之 ICP算法和RANSAC算法

临时研究了下机器视觉两个基本算法的算法原理 ,可能有理解错误的地方,希望发现了告诉我一下 主要是了解思想,就不写具体的计算公式之类的了 (一) ICP算法(Iterative Closest Point迭代最近点) ICP(Iterative Closest Point迭代最近点)算法是一种点集对点集配准方法,如下图1 如下图,假设PR(红色块)和RB(蓝...

2.5 整数和算法

2.5.1 引言   正如2.1节所说, 算法这一术语最初指的是用整数的十进制法表示的用法进行算术运算的过程。修改后能处理二进制表示的这些算法是计算机算术的基础。这些算法为理解算法这一概念及算法复杂度提供了很好的实例。因此本书将讨论这些算法。   除算术中常用的整数算法以外,还有许多涉及整数的算法,包括欧里几德算法,这是最有用的算法之一,很可能是数学中最古...