用回溯法解决0-1背包问题需要解决一下问题:
1.如何动态生成子集树
2.如何设计子集树中的结点类型
3.如何设计两个剪枝函数:约束函数和限界函数
4.如何保存一个或多个最优解,同时保存最优值
解决方法:
1.子集树通过动态的方式生成,子集树中的结点类型共用物品类型,其中结点之间的父子关系通过递归调用的方式关联,这种关系并不在类中设置变量显示表示。
2.为了方便限界函数的计算和程序中的使用,先对物品预处理,以物品的单位价值重量进行降序排序。
3.设置程序运行时一个构造最优解变量,该变量对应的最优值与当前最优解对于的最优值对比,如果优于当前最优解,则将覆盖当前最优解;如果与当前最优解对应的值相等,则同时保存两个最优解。
以下是具体的源代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
typedef int Typew;
typedef int Typep;
//物品类
class Object{
friend class Knap;
public:
int operator <= (Object a) const{
return (d >= a.d);
}
private:
int ID; //物品编号
Typew w; //物品重量
Typep p; //物品价值
float d; //单位重量价值
};
//0-1背包问题的主类
class Knap{
public:
Knap(Typew *w, Typep *p, Typew c, int n);
Typep Knapsack();//回溯法解决0-1背包问题的主函数
//回溯法求解01背包问题
void BackTrack(int floor);
//负责打印最优值和最优解,以物品编号的顺序打印结果
void print();
private:
//计算结点价值上界
Typep Bound(int i);
Typew c; //背包容量
int n; //物品总数
Object *Q; //在Q数组中存放的物品以单位重量价值降序排序
Typew cw; //当前装包重量
Typep cp; //当前装包价值
int *cbestx; //当前最优解
int count; //最优解的个数
int *bestx[10]; //最优解,最优解的个数不超过10个。
Typep bestp; //最优值
Typep oldbestp; //用于回溯法边界处理,保存上一次最优值
};
Knap::Knap(Typew *w, Typep *p, Typew c, int n)
{
//初始化
Typew W = 0;
Typep P = 0;
count = 0;
this->c = c;
oldbestp = 0;
this->n = n;
cw = 0;
cp = 0;
Q = new Object[n];
for(int i =0; i<n; i++)
{
Q[i].ID = i+1;
Q[i].d = 1.0*p[i]/w[i];
Q[i].w = w[i];
Q[i].p = p[i];
P += p[i];
W += w[i];
}
//所有物品的总重量小于等于背包容量c
if (W <= c)
{
bestp = P;
int *newbestx = new int[n];
for(int i =0; i<n; i++)
{
newbestx[i] = 1;
}
bestx[count++] = newbestx;
}
//所有物品的总重量大于背包容量c,存在最佳装包方案
//采用简单冒泡排序
for(int i = 0; i<n-1; i++)
for(int j = 0; j<n-i-1; j++)
{
if(Q[j].d < Q[j+1].d)
{
Object temp = Q[j];
Q[j] = Q[j+1];
Q[j+1] = temp;
}
}
}
Typep Knap::Knapsack()
{
if(count > 0) //背包容量足够大,在初始化时已经将所有物品装入背包
{
print();
return bestp;
}
else //背包容量小于物品所有重量,存在最优装包方案
{
cbestx = new int[n];
BackTrack(0); //从数组Q下标0,首结点开始回溯法求解
}
}
void Knap::BackTrack(int floor)
{
if(floor > n-1) //已经到了子集树中的叶子结点
{
if( cp == oldbestp ) //说明可能有多个最优解
{
int *newbe = new int[n];
for (int i = 0; i < n; i++)
{
newbe[i] = cbestx[i];
}
bestx[count++] = newbe;
}
if( cp > oldbestp) //说明最优解需要更新同时只有一个
{
count = 0;
int *newbe = new int[n];
for (int i = 0; i < n; i++)
{
newbe[i] = cbestx[i];
}
bestx[count++] = newbe;
oldbestp = cp;
}
}
else
{
//选取数组Q下标为floor的物品,满足背包容量约束
if (c >= cw + Q[floor].w)
{
cw += Q[floor].w;
cp += Q[floor].p;
if(cp >= bestp)
bestp = cp;
cbestx[floor] = 1;
BackTrack(floor + 1);
cw -= Q[floor].w;
cp -= Q[floor].p;
}
//舍去数组Q下标为floor的物品,满足限界函数
if(cp + Bound(floor + 1) >= bestp)
{
cbestx[floor] = 0;
BackTrack(floor + 1);
}
}
}
void Knap::print()
{
Typep *original = new int[n+1];
cout<<"以下每行为一种解法:"<<endl;
for (int i = count-1; i >= 0; i--)
{
for (int j = 0; j < n; j++)
{
original[Q[j].ID] = bestx[i][j];
}
for (int k = 1; k <= n; k++)
{
cout<< original[k] <<" ";
}
cout<<endl;
}
cout<<"最优解的个数:"<<count<<endl;
cout<<"最优值:"<<bestp<<endl;
}
Typep Knap::Bound(int i)
{
Typew cleft = c - cw;
Typep b = cp;
while (i < n && Q[i].w <= cleft)
{
cleft -= Q[i].w;
b += Q[i].p;
i++;
}
if(i < n) b += Q[i].p/Q[i].w * cleft;
return b;
}
int _tmain(int argc, _TCHAR* argv[])
{
const int N = 4;
Typew c = 7;
Typew w[N] = {2,3,5,2};
Typep p[N] = {6,4,8,4};
cout<<"背包容量:"<<c <<" ,物品总数:"<< N<<endl;
cout<<"物品重量数组:";
for (int i = 0; i < N; i++)
{
cout<<w[i]<<" ";
}
cout<<endl;
cout<<"物品价值数组:";
for (int i = 0; i < N; i++)
{
cout<<p[i]<<" ";
}
cout<<endl;
Knap K(w, p, c, N);
K.Knapsack();
K.print();
system("pause");
return 0;
}
运行结果如下图: