用优先队列式分支限界法解决0-1背包问题的算法思想:
1.分支限界法常以广度优先或最小耗费优先(最大效益优先)方式搜索问题的解空间树, 对于0-1背包问题的解空间树是一个颗子集树。
2.在分支限界法中有一个活结点表,活结点表中的每个活结点只有一次机会成为扩展结点,一旦成为 扩展结点就一次性产生所有儿子结点,在这些儿子结点中,导致不可行解或导致非最优解的儿子 结点被舍弃,其余儿子结点被加入到活结点表中。对于0-1背包问题中的每个活结点只有两个儿子 结点,分别表示对物品i的选取和对物品i的舍去;在判断儿子结点是否能加入到活结点表中,有两个 函数需要满足,第一个称为约束函数,判断能否满足背包容量约束,第二个称为限界函数,判断 是否可能有最优解。
3.为了尽快找到0-1背包问题的解,每次选取下一个活结点成为扩展结点的判断依据是当前情况下 最有可能找到最优解的下一个结点。因此,每次选择扩展结点的方法:当前情况下,在活结点表中 选择活结点的上界uprofit(通过限界函数Bound求出)最大的活结点成为当前的扩展结点。 这一过程一直持续到找到所需的解或活结点表为空时为止。这个过程体现出分支限界法以“最大 效益优先”方式进行。
4.为了在活结点表中选择拥有最大的上界uprofit的活结点,在活结点表上实现优先队列。
5.通过上述第3点,可以求出0-1背包问题的最优值。为了求出0-1背包问题的最优解,对于每一个在 活结点表中的活结点创建一个树结点,树节点需要反映该结点的父节点和是否有左孩子(有左孩子 表示物品i选取了,没有左孩子表示物品i舍去了)。因此,可以构造一颗子集树,最优解就是从树根 到叶子结点的路径,子集树的第i层的所有结点就是在不同情况下对物品i的取舍结点。构造最优解的 顺序是从叶子结点到根结点的过程。
从上述算法思想中,得出必须解决的问题:
1.优先队列式的活结点表
2.活结点表对应的子集树
算法涉及的函数功能:
1.建立一个最大堆、初始化最大堆、在最大堆中插入一个元素和在最大堆中取出最大元素
2.求解0-1背包问题的主函数Knapsack
3.向子集树和最大堆中插入结点函数AddLiveNode
4.计算结点价值上界函数Bound,为了方便,需要对物品以单位价值量排序
5.负责求解0-1背包问题的最优值和最优解函数MaxKnapsack
算法涉及的类:
1.树结点类,用于构造子集树,以便计算最优解
2.堆结点类,用于定义堆元素类型,便于MaxKnapsack函数使用
3.最大堆类,用于实现优先队列
4.物品类,用于保存物品编号和物品的单位重量价值
5.解决0-1背包问题的主类
1.分支限界法常以广度优先或最小耗费优先(最大效益优先)方式搜索问题的解空间树, 对于0-1背包问题的解空间树是一个颗子集树。
2.在分支限界法中有一个活结点表,活结点表中的每个活结点只有一次机会成为扩展结点,一旦成为 扩展结点就一次性产生所有儿子结点,在这些儿子结点中,导致不可行解或导致非最优解的儿子 结点被舍弃,其余儿子结点被加入到活结点表中。对于0-1背包问题中的每个活结点只有两个儿子 结点,分别表示对物品i的选取和对物品i的舍去;在判断儿子结点是否能加入到活结点表中,有两个 函数需要满足,第一个称为约束函数,判断能否满足背包容量约束,第二个称为限界函数,判断 是否可能有最优解。
3.为了尽快找到0-1背包问题的解,每次选取下一个活结点成为扩展结点的判断依据是当前情况下 最有可能找到最优解的下一个结点。因此,每次选择扩展结点的方法:当前情况下,在活结点表中 选择活结点的上界uprofit(通过限界函数Bound求出)最大的活结点成为当前的扩展结点。 这一过程一直持续到找到所需的解或活结点表为空时为止。这个过程体现出分支限界法以“最大 效益优先”方式进行。
4.为了在活结点表中选择拥有最大的上界uprofit的活结点,在活结点表上实现优先队列。
5.通过上述第3点,可以求出0-1背包问题的最优值。为了求出0-1背包问题的最优解,对于每一个在 活结点表中的活结点创建一个树结点,树节点需要反映该结点的父节点和是否有左孩子(有左孩子 表示物品i选取了,没有左孩子表示物品i舍去了)。因此,可以构造一颗子集树,最优解就是从树根 到叶子结点的路径,子集树的第i层的所有结点就是在不同情况下对物品i的取舍结点。构造最优解的 顺序是从叶子结点到根结点的过程。
从上述算法思想中,得出必须解决的问题:
1.优先队列式的活结点表
2.活结点表对应的子集树
算法涉及的函数功能:
1.建立一个最大堆、初始化最大堆、在最大堆中插入一个元素和在最大堆中取出最大元素
2.求解0-1背包问题的主函数Knapsack
3.向子集树和最大堆中插入结点函数AddLiveNode
4.计算结点价值上界函数Bound,为了方便,需要对物品以单位价值量排序
5.负责求解0-1背包问题的最优值和最优解函数MaxKnapsack
算法涉及的类:
1.树结点类,用于构造子集树,以便计算最优解
2.堆结点类,用于定义堆元素类型,便于MaxKnapsack函数使用
3.最大堆类,用于实现优先队列
4.物品类,用于保存物品编号和物品的单位重量价值
5.解决0-1背包问题的主类
以下是具体的代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
typedef int Typew;
typedef int Typep;
//物品类
class Object{
friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);
public:
int operator <= (Object a) const{
return (d >= a.d);
}
private:
int ID; //物品编号
float d; //单位重量价值
};
//树结点类
class bbnode{
friend class Knap;
friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);
private:
bbnode *parent; //指向父节点的指针
int LChild; //如果是左儿子结点取1,也即说明该物品已装进背包
};
//堆结点类
class HeapNode{
friend class Knap;
friend class MaxHeap;
public:
operator Typep()const{return uprofit;};
private:
Typep uprofit, //结点的价值上界
profit; //结点所相应的价值
Typew weight; //结点所相应的重量
int level; //活结点在子集树中所处的层序号
bbnode *elemPtr; //指向该活结点在子集树中相应结点的指针
};
//最大堆类
class MaxHeap{
public:
MaxHeap(int maxElem)
{
HeapElem = new HeapNode* [maxElem+1]; //下标为0的保留
capacity = maxElem;
size = 0;
}
void InsertMax(HeapNode *newNode);
HeapNode DeleteMax(HeapNode* &N);
private:
int capacity;
int size;
HeapNode **HeapElem;
};
//0-1背包问题的主类
class Knap{
//Knapsack主函数功能:解决初始化、求解最优值和最优解、回收内存
friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);
public:
Typep MaxKnapsack();
private:
MaxHeap *H;
//Bound辅助Maxknapsack函数:计算结点价值上界
Typep Bound(int i);
//AddLiveNode辅助Maxknapsack函数:将活结点插入子集树和优先队列中
void AddLiveNode(Typep up, Typep cp, Typew cw, int ch, int level);
bbnode *E; //指向扩展结点的指针
Typew c; //背包容量
int n; //物品总数
Typew *w; //物品重量数组(以单位重量价值降序)
Typep *p; //物品价值数组(以单位重量价值降序)
Typew cw; //当前装包重量
Typep cp; //当前装包价值
int *bestx; //最优解
};
void MaxHeap::InsertMax(HeapNode *newNode)
{
//极端情况下暂未考虑,比如堆容量已满等等
int i = 1;
for (i = ++size; i/2 > 0 && HeapElem[i/2]->uprofit < newNode->uprofit; i /= 2)
{
HeapElem[i] = HeapElem[i/2];
}
HeapElem[i] = newNode;
}
HeapNode MaxHeap::DeleteMax(HeapNode *&N)
{
//极端情况下暂未考虑
if(size >0 )
{
N = HeapElem[1];
//从堆顶开始调整
int i = 1;
while(i < size)
{
if(((i*2 +1) <= size) && HeapElem[i*2]->uprofit > HeapElem[i*2 +1]->uprofit)
{
HeapElem[i] = HeapElem[i*2];
i = i*2;
}
else
{
if(i*2 <= size)
{
HeapElem[i] = HeapElem[i*2];
i = i*2;
}
else
break;
}
}
if(i < size)
HeapElem[i] = HeapElem[size];
}
size--;
return *N;
}
Typep Knap::MaxKnapsack()
{
H = new MaxHeap(1000);
bestx = new int [n+1];
//初始化,为处理子集树中的第一层做准备,物品i处于子集树中的第i层
int i = 1; //生成子集树中的第一层的结点
E = 0; //将首个扩展点设置为null,也就是物品1的父节点
cw = 0;
cp = 0;
Typep bestp = 0; //当前最优值
Typep up = Bound(1); // 选取物品1之后的价值上界
//当选择左儿子结点时,上界约束up不用关心,重量约束wt需要考虑。因为上界约束跟父节点相同。
//当选择右儿子结点时,上界约束up需要考虑,重量约束不需要考虑。因为父节点和该结点重量相同。
while (i != n+1)
{
//检查当前扩展结点的左儿子结点
Typew wt = cw + w[i]; //当前选择物品i之后的总重量wt
if(wt <= c) //背包能将物品i装下,也即当前扩展结点的左儿子结点可行
{
if(cp + p[i] > bestp)
bestp = cp + p[i];
AddLiveNode(up, cp + p[i], cw + w[i], 1, i);
}
//检查当前扩展结点的右儿子结点
up = Bound(i + 1); //未选择物品i之后的价值上界
if(up >= bestp)
AddLiveNode(up, cp, cw, 0, i);
//从优先队列中选择价值上界最大的结点成为扩展结点
HeapNode* N;
H->DeleteMax(N);
E = N->elemPtr;
cw = N->weight;
cp = N->profit;
up = N->uprofit;
i = N->level + 1; //准备生成N.level+1层的子集树结点
}
//从子集树中的某叶子结点开始构造当前最优解
for (int i = n; i > 0; i--)
{
bestx[i] = E->LChild;
E = E->parent;
}
return cp;
}
Typep Knap::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;
}
void Knap::AddLiveNode(Typep up, Typep cp, Typew cw, int ch, int level)
{
bbnode *b=new bbnode;
b->parent=E;
b->LChild=ch;
HeapNode *N = new HeapNode;
N->uprofit=up;
N->profit=cp;
N->weight=cw;
N->level=level;
N->elemPtr=b;
H->InsertMax(N);
}
//Knapsack返回最大价值,最优值保存在bestx
Typep Knapsack(Typew *w, Typep *p, Typew c, int n, int *bestx)
{//数组w、p和bestx中下标为0的元素保留不用
//初始化
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];
}
//所有物品的总重量小于等于背包容量c
if (W <= c)
{
for(int i =1; i<=n; i++)
{
bestx[i] = p[i];
}
return P;
}
//所有物品的总重量大于背包容量c,存在最佳装包方案
//sort(Q,n);对物品以单位重量价值降序排序
//采用简单冒泡排序
for(int i = 1; i<n; i++)
for(int j = 1; j<= n-i; j++)
{
if(Q[j-1].d < Q[j].d)
{
Object temp = Q[j-1];
Q[j-1] = Q[j];
Q[j] = temp;
}
}
Knap 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;
Typep bestp = K.MaxKnapsack();
for(int i = 1; i<=n; i++)
{
bestx[Q[i-1].ID] = K.bestx[i];
}
delete [] Q;
delete [] K.w;
delete [] K.p;
delete [] K.bestx;
delete [] K.H;
return bestp;
}
int _tmain(int argc, _TCHAR* argv[])
{
const int N = 4;
Typew c=8; //背包容量
int bestx[N+1]; //最优解
int bestp; //最优值
//需要说明的是,这里的数组p[]和w[]的第一个元素是-1,这是因为我在操作过程中
//都是从数组元素的1开始的,而我们知道数组中第一个元素是0号元素,所以我这里用-1填上
Typep p[]={-1,6,4,7,4};//物品价值
Typew w[]={-1,2,3,5,2};//物品重量
bestp = Knapsack(w, p, c, N, bestx);
cout<<"物品总数N = "<< N<<",背包容量C = "<< c<<endl;
for (int i = 1; i <= N; i++)
{
if(i ==1 ) cout<<"重量数组:";
cout<<w[i];
if(i != N) cout<<",";
else
cout<<endl;
}
for (int i = 1; i <= N; i++)
{
if(i ==1 ) cout<<"价值数组:";
cout<<p[i];
if(i != N) cout<<",";
else
cout<<endl;
}
for (int i = 1; i <= N; i++)
{
if(i ==1 ) cout<<"是否选取:";
cout<<bestx[i];
if(i != N) cout<<",";
else
cout<<endl;
}
cout<<"背包最优价值:"<<bestp<<endl;
system("pause");
return 0;
}
运行结果如下图:
来源:CSDN
作者:峰中劲草
链接:https://blog.csdn.net/qq_24059821/article/details/51253704