背包问题(Knapsackproblem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。这个问题涉及到了两个条件:一是物品总的大小小于或等于背包的大小,二是物品总的价值要尽量大。
一.采用递归的回溯法
刚开始接触此类问题时,很多人都会想到用回溯法解决,也就是用递归,这是最直接的方法,同八皇后、迷宫、组合、全排列、贪吃蛇等问题一样,下面给我本人开始用递归写出的算法:
//采用递归解决背包问题
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Fruit
{
private:
string name;
int size;
int price;
public:
Fruit(string name,int size,int price)
{
this->name=name;
this->size=size;
this->price=price;
}
string getName(){
return name;
}
int getPrice(){
return price;
}
int getSize(){
return size;
}
};
#define BOUNCE 10 //最大装入数量
#define N 10 //水果的个数
Fruit fruits[]={
Fruit("李子", 4, 4500),
Fruit("苹果", 5, 4700),
Fruit("橘子", 2, 2250),
Fruit("草莓", 1, 1100),
Fruit("甜瓜", 6, 4940),
Fruit("菠萝", 2, 3900),
Fruit("西瓜", 6, 5800),
Fruit("桃子", 3, 3700),
Fruit("香蕉", 2, 3750),
Fruit("梨子", 3, 3600)
};
vector<Fruit> selectedCompose; //当前最优选择序列
int maxPrice; //当前最大总价格
void Search(int begin, int sumSize, int sumPrice, vector<Fruit> v)
{
for (int i = begin; i < N; i++)
{
int s = sumSize + fruits[i].getSize();
int t = sumPrice + fruits[i].getPrice();
if (s == BOUNCE) //当前背包的装入数量已经达到最大
{
v.push_back(fruits[i]);
cout<<"总价值:"<<t<<"\t";
for (int i = 0; i < v.size(); i++) //把每种总量为N的组合都打印出来,方便测试
{
cout<<v[i].getName()<<"\t";
}
cout<<endl;
if (t > maxPrice)
{
selectedCompose = v;//select始终是价格总数最大的那个组合
maxPrice = t; //maxPrice始终存放最高总价格
}
v.pop_back();//将最后一个元素删除,以寻找下一个可能的匹配
}
if (s < BOUNCE)
{
v.push_back(fruits[i]);
Search(i + 1, s, t, v); //递归
v.pop_back();//回溯,要恢复递归之前得状态
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
vector<Fruit> v;
maxPrice = 0;
cout<<"所有总量为 "<<N<<"的组合为:"<<endl;
cout<<"------------------------------------------"<<endl;
Search(0, 0, 0, v);
cout<<"------------------------------------------"<<endl;
cout<<"在总量为"<<N<<"的情况下,总价值最大的组合为:"<<endl;
for (int i = 0; i < selectedCompose.size(); i++)
{
cout<<selectedCompose[i].getName()<<"\t";
}
cout<<endl<<"总价值为: "<<maxPrice<<endl;
system("pause");
return 0;
}
基本原理很简单:求出满足总量的所有组合,再找出总价值最大的那个组合!
递归算法虽然方便,但是所需时间复杂度很高。该算法的时间复杂度为O(n2^n)
二.动态规划
动态规划方法建立在最优原则的基础上,是用空间换时间的一种方法的抽象。其关键是发现子问题和记录其结果。然后利用这些
结果减轻运算量。
如果我们用子问题定义状态来描述的话可以这样解释:
用f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。用公式表示:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
具体的解释可以理解为将前i件物品放入容量为v的背包中,现只考虑第i件物品的策略(放或不放),那么就可以转化为一个只涉及前i-1件物品和第i件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。(v表示背包的最大容量,c[i]表示第i件物品的大小,w[i]表示第i件物品的价值)
// KnapsackProblem.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <string>
using namespace std;
#define MAX 8
#define MIN 1
class Fruit
{
private:
string name;
int size;
int price;
public:
Fruit(string name,int size,int price)
{
this->name=name;
this->size=size;
this->price=price;
}
string getName(){
return name;
}
int getPrice(){
return price;
}
int getSize(){
return size;
}
};
void main()
{
int item[MAX+1];
int value[MAX+1];
Fruit fruits[]={
Fruit("李子", 4, 4500),
Fruit("苹果", 5, 4700),
Fruit("橘子", 2, 2250),
Fruit("草莓", 1, 1100),
Fruit("甜瓜", 6, 3700),
Fruit("菠萝", 2, 4900),
Fruit("西瓜", 3, 5800)
};
for (int i = 0; i < MAX+1; i++)
{
item[i] = 0;
value[i] = 0;
}
for(int i = 0; i < 7; i++)
{
for(int s = fruits[i].getSize();s <= MAX; s++)
{
//s表示现在背包的大小
int p = s-fruits[i].getSize(); //表示每次增加单位背包空间,背包所剩的空间
int newvalue = value[p] + fruits[i].getPrice(); //value[p]表示增加的背包空间可以增加的价值,fruits[i].getprice()表示原有的背包的价值
if(newvalue > value[s])
{
//现有的价值是否大于背包为s时的价值
value[s] = newvalue;
item[s] = i;//将当前的水果项添加到背包的物品中
}
}
}
cout<<"物品\t价格"<<endl;
for(int i = MAX; i > MIN; i = i - fruits[item[i]].getSize())
{
cout<<fruits[item[i]].getName() << "\t"<< fruits[item[i]].getPrice()<<endl;
}
cout<<"合计\t"<<value[MAX];
system("pause");
}