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}—>{}


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

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

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

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


测试数据:

背包容量为30

物品重量和价值分别为:

16,45

15,25

15,25

程序运行结果如下:

?

?

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

时间: 2024-10-09 03:36:34

0-1背包问题的分枝—限界算法的相关文章

分枝限界算法

1.算法基本思想 分枝限界方法採用宽度优先的方式搜索解空间树, 将活结点存放在一个特殊的表中. 在扩展结点处,先生成全部儿子结点,将那些导致不可行解或非最优解的儿子舍弃.其余儿子增加活结点表中. 此后, 从活结点表中依照某种规则取出一个结点作为当前扩展结点,继续搜索. 2.分类 从活结点表中选择下一扩展结点的不同方式导致不同的分枝限界方法.最常见的有下面两种方式: (1)队列式分枝限界方法 将活结点表组织成一个队列, 按先进先出原则选取下一个结点作为当前扩展结点.队列式分枝限界方法的搜索方式类似

背包问题:0/1背包问题 普通背包问题(贪心算法只适用于普通背包问题)

//sj和vj分别为第j项物品的体积和价值,W是总体积限制. //V[i,j]表示从前i项{u1,u2,…,un}中取出来的装入体积为j的背包的物品的最大价值. 第一种:0/1背包问题 最大化 ,受限于  1)若i=0或j=0,  V[i,j] = 0 2)若j<si, V[i,j] = V[i-1,j] 3)若i>0且j>=si, V[i,j] = Max{V[i-1,j],V[i-1,j-si]+vi} 第二种:背包问题:在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部

动态规划算法求解0,1背包问题

首先我们来看看动态规划的四个步骤: 1. 找出最优解的性质,并且刻画其结构特性: 2. 递归的定义最优解: 3. 以自底向上的方式刻画最优值: 4. 根据计算最优值时候得到的信息,构造最优解 其中改进的动态规划算法:备忘录法,是以自顶向下的方式刻画最优值,对于动态规划方法和备忘录方法,两者的使用情况如下: 一般来讲,当一个问题的所有子问题都至少要解一次时,使用动态规划算法比使用备忘录方法好.此时,动态规划算法没有任何多余的计算.同时,对于许多问题,常常可以利用其规则的表格存取方式,减少动态规划算

动态规划算法实现部分——0/1背包问题

代码: import java.util.*; import java.util.Scanner; /* *动态规划思想解决0/1背包问题 */ public class Main{ public static void main(String[] args){ Scanner in=new Scanner(System.in); System.out.println("输入背包的容量"); int bagCap=in.nextInt(); //背包的容量 System.out.pri

0/1背包问题(回溯法)

回溯法是一个既带有系统性又带有跳跃性的搜索算法.它在包含问题的所有解的解空间树中,按深度优先策略,从根结点出发搜索解空间树.算法搜索至解空间树的任意一结点时,先判断该结点是否包含问题的解.如果肯定不包含,则跳过对该结点为根的子树搜索,逐层向其祖先结点回溯:否则 ,进入该子树,继续按深度优先策略搜索. 问题的解空间 用回溯法解问题时,应明确定义问题的解空间.问题的解空间至少包含问题的一个(最优)解.对于 n=3 时的 0/1 背包问题,可用一棵完全二叉树表示解空间,如图所示: 求解步骤 1)针对所

0/1背包问题的动态规划法求解 —— Java 实现

0/1背包问题的动态规划法求解,前人之述备矣,这里所做的工作,不过是自己根据理解实现了一遍,主要目的还是锻炼思维和编程能力,同时,也是为了增进对动态规划法机制的理解和掌握. 值得提及的一个问题是,在用 JAVA 实现时, 是按算法模型建模,还是用对象模型建模呢? 如果用算法模型,那么 背包的值.重量就直接存入二个数组里:如果用对象模型,则要对背包以及背包问题进行对象建模.思来想去,还是采用了对象模型,尽管心里感觉算法模型似乎更好一些.有时确实就是这样,对象模型虽然现在很主流,但也不是万能的,采用

第十六章 贪心算法——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)是下面相应子问题的一个最优解:

【算法设计与分析】7、0/1背包问题,动态规划

/** * 书本:<算法分析与设计> * 功能:给定n种物品和一个背包,物品i的重量是Wi, 其价值为Vi,问如何选择装入背包的物品,使得装入背包的物品的总价值最大? * 文件:beiBao.cpp * 时间:2014年11月30日19:22:47 * 作者:cutter_point */ #include <iostream> #define SIZEBEIBAO 20 using namespace std; //这个背包问题的最优的子结构是 /* 首先这里一共有m种物品,背包

【计算机算法与分析】——7.1分枝-限界法

结论: 分支限界算法的思想通过本例子加以体现,明显这种方法是可行的,比盲目的查找结点有用,但是其效果(查找结点的个数)没有回溯算法有效,只能说明回溯算法在此问题上比朴素的分支限界算法(基于前面的限界函数)较优,接下来的一些改进或许能进一步提升分支限界算法的效果. 原文地址:https://www.cnblogs.com/chihaoyuIsnotHere/p/9931953.html