本来这次是要学习字符串的最短编辑距离的,但是由于最近正在研究《linux服务器高性能编程》以及实验室项目验收,所以没有时间对这个进行深入理解,所以暂时搁置一段时间,这次来学习一下0-1背包问题,以及找零问题。对于这两个问题都属于动态规划的经典问题。
0-1背包问题:
假如有容量为10(克)的包和重量为3,4,5(克)的物品,每件物品的价值为4,5,6.先求在容量不超过10克的情况下,包中物品的总价值最高。
找零问题:
假如收银员需要给顾客找26块钱现在,有面值1,2,3,21,25币钞(每种币钞的数量无限),现在如何选择才能使使用的币钞个数最少。
我们先来分析找零问题.既然是动态规划,那么必须会要求我们找出子问题,以及使用数组存储子问题。题目要求我们找出26块钱使用的最少,那我们的子问题就是25,24......到1,每种情况所使用的币钞的最少数量,所以我们需要大小为26的数组存储中间过程。我们的递归方程描述为,MinTemp=min(MinTemp,coins[i - value[j] + 1),MinTemp是我们使用的最少硬币的个数,value数组是我们存储面值的数组即 1,2,3,21,25(每种面值的货币数量无限)。coins数组存储MinTemp,如coins[4]表示构造面值为4的货币需要的最少币钞数。这样我们再来看一下coins[i - value[j]] + 1,其表示当我们构造值为i的货币时,如果我们要用value[j]中所存储的那个货币,那么我们需要知道构造 i - value[j]所需要的最小货币数,再加value【j】(就是后面那个1),这样我们每次取最小存储在MinTemp,最后将其存在coins[i],当然在计算前我们必须满足i - value[j] >0.下面我们来给出实现代码。
#include<stdio.h>
#define VALUE 105
#define Size 5
const value[] = {1,2,5,21,25};
int coinsUsed[VALUE] = {0};
int coinsTrack[VALUE] = {0};
void MakeChange()
{
int i,j;
int coinvalues = 0;
for(i = 1; i<VALUE; i++)
{
int MinTemp = VALUE;
for(j = 0; j<Size; j++)
{
if(i >= value[j] ) //如果i < value[j] 那么我们肯定不能用value【j】就好像1钱不可能用两块钱来找吧
{
if(MinTemp >= coinsUsed[i - value[j]])
{
MinTemp =coinsUsed[i - value[j]];
coinvalues = value[j];
}
}
}
coinsUsed[i] = MinTemp + 1;
coinsTrack[i] = coinvalues; //该数组用于存储所运用货币
}
}
int main()
{
int i = 0;
MakeChange();
for(i = 1; i < VALUE; i++)
printf("%d ",coinsUsed[i]);
printf("\n");
int temp = VALUE-1;
while(temp > 0)
{
printf("%d ", coinsTrack[temp]); //输出所用的货币
temp = temp - coinsTrack[temp];
}
printf("\n");
}
对于0-1背包问题如果我们不限物品的个数,那么那么我们的问题就和上述问题没什么区别,只不过将上述加1改成该物品的价值price【i】,再做稍微的调整。对于每件物品只有一件,那么就不能使用上述动态方程。我们的动态方程变为
c[i][j] = MAX(c[i-1][j], c[i][j-weight[i] + price[j]).
上述图片是我从我网上找到的一幅图很好的解释图片,下面我来做一下解释。
上图j表示背包的容量,i表示物品的下标,也就是物品的重量weight的下标。c[i][j]表示将有i个物品,背包容量为j时最大价值。
MAX(c[i-1][j], c[i -1][j -weight[i]]+price[i]).其表示如果我们不用第i个物品那么容量为j的物品的最大价值为c[i -1][j],如果我们要用第j个物品那么其价值为c[i-1][j - weight[i]] + price[i],j - weight[i]表示如果我们要用weight【i】那么我们还需知道容量为j - weight[i]的最大价值(实际编程时注意给下标从0开始)。
#include<stdio.h>
#define MAX(a,b) (((a) > (b))? (a) : (b))
int c[4][11] = {0}; //用于存储packet1的最大价值数组(当每件物品只有一个时)
int USED[11] = {0}; //用于packet2的最大价值数组(每件物品有多个是,类似于找零问题)
int w[3] = {3,4,5}; //物品重量
int price[4] = {4,5,6}; //物品价值
void packet1(int N, int CONTATINER) //每件物品只有一个时
{
int i, j;
for(i =1; i < N + 1; i++)
{
for(j = 1; j < CONTATINER + 1; j++)
{
if(w[i-1] <= j)
c[i][j] = MAX(c[i-1][j], c[i-1][j-w[i-1]] + price[i-1]);
else
c[i][j] = c[i-1][j];
}
}
}
void packet2(int N, int CONTATINAER) //每件物品有多个时
{
int temp ;
int i, j;
for(i =1; i < N + 1; i++)
{
temp = 0;
for(j = 0; j < CONTATINAER; j++)
{
if(i >= w[j])
{
if(temp < USED[i-w[j]] + price[j])
temp = USED[i - w[j]] + price[j];
}
}
USED[i] = temp;
}
}
int main()
{
int i;
int j;
packet2(10,3);
for(i =0; i < 10 + 1; i++)
printf("%d ", USED[i]);
printf("\n");
printf("_______________________________________\n");
packet1(3, 10);
for(i = 0; i < 4; i++)
for(j = 0; j < 11; j++)
{
printf("%d ", c[i][j]);
if(j==10)
printf("\n");
}
}