1. 动态规划
以下关于动态规划的文字描述来源
动态规划之背包问题(一)
作者:Hawstein
一切都要从一则故事说起。
话说有一哥们去森林里玩发现了一堆宝石,他数了数,一共有n个。 但他身上能装宝石的就只有一个背包,背包的容量为C。这哥们把n个宝石排成一排并编上号: 0,1,2,…,n-1。第i个宝石对应的体积和价值分别为V[i]和W[i] 。排好后这哥们开始思考: 背包总共也就只能装下体积为C的东西,那我要装下哪些宝石才能让我获得最大的利益呢?
OK,如果是你,你会怎么做?你斩钉截铁的说:动态规划啊!恭喜你,答对了。 那么让我们来看看,动态规划中最最最重要的两个概念: 状态和状态转移方程在这个问题中分别是什么。
我们要怎样去定义状态呢?这个状态总不能是凭空想象或是从天上掉下来的吧。 为了方便说明,让我们先实例化上面的问题。一般遇到n,你就果断地给n赋予一个很小的数, 比如n=3。然后设背包容量C=10,三个宝石的体积为5,4,3,对应的价值为20,10,12。 对于这个例子,我想智商大于0的人都知道正解应该是把体积为5和3的宝石装到背包里, 此时对应的价值是20+12=32。接下来,我们把第三个宝石拿走, 同时背包容量减去第三个宝石的体积(因为它是装入背包的宝石之一), 于是问题的各参数变为:n=2,C=7,体积{5,4},价值{20,10}。好了, 现在这个问题的解是什么?我想智商等于0的也解得出了:把体积为5的宝石放入背包 (然后剩下体积2,装不下第二个宝石,只能眼睁睁看着它溜走),此时价值为20。 这样一来,我们发现,n=3时,放入背包的是0号和2号宝石;当n=2时, 我们放入的是0号宝石。这并不是一个偶然,没错, 这就是传说中的“全局最优解包含局部最优解”(n=2是n=3情况的一个局部子问题)。 绕了那么大的圈子,你可能要问,这都哪跟哪啊?说好的状态呢?说好的状态转移方程呢? 别急,它们已经呼之欲出了。
我们再把上面的例子理一下。当n=2时,我们要求的是前2个宝石, 装到体积为7的背包里能达到的最大价值;当n=3时,我们要求的是前3个宝石, 装到体积为10的背包里能达到的最大价值。有没有发现它们其实是一个句式!OK, 让我们形式化地表示一下它们, 定义d(i,j)为前i个宝石装到剩余体积为j的背包里能达到的最大价值。 那么上面两句话即为:d(2, 7)和d(3, 10)。这样看着真是爽多了, 而这两个看着很爽的符号就是我们要找的状态了。 即状态d(i,j)表示前i个宝石装到剩余体积为j的背包里能达到的最大价值。 上面那么多的文字,用一句话概括就是:根据子问题定义状态!你找到子问题, 状态也就浮出水面了。而我们最终要求解的最大价值即为d(n, C):前n个宝石 (0,1,2…,n-1)装入剩余容量为C的背包中的最大价值。状态好不容易找到了, 状态转移方程呢?顾名思义,状态转移方程就是描述状态是怎么转移的方程(好废话!)。 那么回到例子,d(2, 7)和d(3, 10)是怎么转移的?来,我们来说说2号宝石 (记住宝石编号是从0开始的)。从d(2, 7)到d(3, 10)就隔了这个2号宝石。 它有两种情况,装或者不装入背包。如果装入,在面对前2个宝石时, 背包就只剩下体积7来装它们,而相应的要加上2号宝石的价值12, d(3, 10)=d(2, 10-3)+12=d(2, 7)+12;如果不装入,体积仍为10,价值自然不变了, d(3, 10)=d(2, 10)。记住,d(3, 10)表示的是前3个宝石装入到剩余体积为10 的背包里能达到的最大价值,既然是最大价值,就有d(3, 10)=max{ d(2, 10), d(2, 7)+12 }。好了,这条方程描述了状态d(i, j)的一些关系, 没错,它就是状态转移方程了。把它形式化一下:
d(i,j)=max{d(i?1,j),d(i?1,j?V[i?1])+W[i?1]}
注意讨论前i个宝石装入背包的时候, 其实是在考查第i-1个宝石装不装入背包(因为宝石是从0开始编号的)。至此, 状态和状态转移方程都已经有了。
2. 贪心算法
如上述是一个 0-1 背包问题,即对一个宝石只能拿或不拿。还有一种是分数背包问题,即可以拿走宝石的一部分,因此可以优先拿走所有单位价值最高的宝石。故此处是一个贪心算法问题。
3. C++代码实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Good {
public:
int num;
int weight;
int value;
float unit_value;
Good(int n, int w, int v) : num(n), weight(w), value(v) {
if (w == 0) unit_value = 0;
else unit_value = (float)v / (float)w;
};
};
class Bag{
public:
// 0-1背包问题:对一件物品拿或不拿得到最大价值
void OneZeroBag(vector<Good>goods, const int W) {
int n = goods.size();
// d[i][j] 表示在前 i 个(0 ~ i-1)宝石中,剩余体积为 j 能达到的最大价值
int **d = New2DMat(n + 1, W + 1);
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= W; j++) {
d[i][j] = (i == 0) ? 0 : d[i - 1][j]; // 0 个宝石价值为0
if (i > 0 && j >= goods[i-1].weight) {
int q = d[i - 1][j - goods[i - 1].weight] + goods[i - 1].value; // 拿第 i 个
d[i][j] = max(d[i][j], q); // 根据价值选择拿或不拿
}
}
}
cout << "最大价值:" << d[n][W] << endl;
// 输出选择结果
cout << "所拿商品编号:";
int j = W;
for (int i = n; i > 0; i--) {
if (d[i][j] > d[i - 1][j]) {
j -= goods[i - 1].weight;
cout << goods[i - 1].num << " ";
}
}
cout << endl;
}
// 分数背包问题:假设物品可以被分割
void FractionalBag(vector<Good> &goods, const int W) {
SortGood(goods, 0, goods.size() - 1); // 按单位价值从高到低排序
int m = 0;
for (int i = 0; i < goods.size(); i++) {
int take_weight = min(goods[i].weight, W - m);
cout << "拿走商品 "<< goods[i].num<<" 的重量是 "<< take_weight << endl;
m += take_weight;
if (m == W)
break;
}
}
private:
void SortGood(vector<Good> &goods, int l, int r) {
if (l >= r)
return;
int m = (l + r) / 2;
SortGood(goods, l, m);
SortGood(goods, m + 1, r);
MergeAB(goods, l, m, r);
};
void MergeAB(vector<Good> &goods, int l, int m, int r) {
vector<Good>temp;
int i, j, k;
for (i = l; i <= m; i++)
temp.push_back(goods[i]);
for (j = r; j > m; j--)
temp.push_back(goods[j]);
i = l, j = r;
k = l;
while (i <= j) {
if (temp[i].unit_value > temp[j].unit_value)
goods[k++] = temp[i++];
else
goods[k++] = temp[j--];
}
};
int** New2DMat(int row, int col) {
int **Mat = new int*[row];
for (int i = 0; i < row; i++)
Mat[i] = new int[col];
return Mat;
}
};
int main()
{
Good g1(1, 5, 20);
Good g2(2, 4, 10);
Good g3(3, 3, 12);
Good good_array[] = { g1, g2, g3 };
vector<Good> goods(good_array, good_array+3);
const int W = 10;
Bag bag;
cout << "分数背包问题:" << endl;
bag.FractionalBag(goods, W);
cout << "0-1 背包问题(拿或不拿):" << endl;
bag.OneZeroBag(goods, W);
}
输出结果为:
分数背包问题:
拿走商品 3 的重量是 3
拿走商品 1 的重量是 5
拿走商品 2 的重量是 2
0-1 背包问题(拿或不拿):
最大价值:32
所拿商品编号:1 3
[Finished in 0.9s]
版权声明:本文为博主原创文章,未经博主允许不得转载。