您的位置:首页 > 其它

动态规划与背包问题

2015-01-29 12:47 302 查看
       动态规划(Dynamic Programming)是算法的设计方法之一,通常用于最优化问题,此类问题可能有多种可行解,而我们希望找出一个最优的解(最大或最小)。动态规划的设计可以分为以下几个步骤:

         1.描述最优解的结构

         2.递归的定义最优解的值

         3.按自底向上的方式计算最优解的值

         4.由计算出的结果构造一个最优解

        第一至第三步构成了动态规划解的基础,第四步在只要求最优解的值时可以省略。

       下面将动态规划应用到背包问题中。

01背包问题

        问题描述:有n个重量和价值分别为wi和vi的物品,从这些物品中挑选出总重量不超过max_w的物品,求所有挑选方案中价值总和的最大值。例如输入:n = 4;   max_w = 5;  w
= {2, 1, 3, 2};  v
= {3, 2, 4, 2};则输出为 7。

        首先,我们来描述一下最优解的结构:假设用dp(i, j)表示从前i件物品中挑选出总重量不超过j的最大价值,对于第i+1件物品我们有两种选择情况:

        1)在最优方案dp(i+1, j)中选择了第i+1件物品,此时dp(i+1, j) = dp(i, j-w[i]) + v[i];

        2)在最优方案dp(i+1, j)中没有选择第i+1件物品,此时dp(i+1, j) = dp(i, j);

        这样,我们只需要在这两种方案中选择价值总和最大的一种即为最优,于是我们可以得到一个递归定义的最优解的结构:

        dp(i+1, j) =      dp(i, j)   if w[i] > j;  第i+1件物品的总重量超过了j时,我们无法选择该物品

                              max(dp(i, j), dp(i+1, j) = dp(i, j-w[i]) + v[i])   if w[i] <= j;

        我们可以首先得到一个递归的解法,对于每一件物品总是有两种选择情况,所以时间复杂度为O(2^n);

#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))

const int n = 4;
const int max_w = 5;
int w
= {2, 1, 3, 2};
int v
= {3, 2, 4, 2};

int recursion01bag(int i, int j){
if(i == 0)
return 0;
if (j<w[i-1])
return recursion01bag(i-1, j);
return max(recursion01bag(i-1, j), (recursion01bag(i-1, j-w[i-1])+v[i-1]));
}

int main(){
int res = recursion01bag(n, max_w);
printf("%d\n", res);
system("pause");
return 0;
}          下面以一张图来模拟该递归过程



        如果函数的输入参数相同,则结果也应该相同。可以发现,在递归过程中有很多重复计算的过程可以省略,基于这种思想,我们可以一个二维数组来保存结果,如果已经计算过了,就直接返回结果值。这样可以将时间复杂度降为O(n*max_w),也称为记忆搜索。#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))

const int n = 4;
const int max_w = 5;
int w
= {2, 1, 3, 2};
int v
= {3, 2, 4, 2};

int dp[n+1][max_w+1];

int memory01bag(int i, int j){

if (dp[i][j] >= 0)
return dp[i][j];
if(i == 0)
return dp[i][j] = 0;
int res;
if (j<w[i-1])
res = memory01bag(i-1, j);
else
res = max(memory01bag(i-1, j), memory01bag(i-1, j-w[i-1])+v[i-1]);
return dp[i][j] = res;
}

int main(){
memset(dp, -1, sizeof(dp));
int res = memory01bag(n, max_w);
printf("%d\n", res);
system("pause");
return 0;
}       接下来我们采用自底向上的方法计算最优解的值,我们可以用二重循环来实现递归过程:
#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))

const int n = 4;
const int max_w = 5;
int w
= {2, 1, 3, 2};
int v
= {3, 2, 4, 2};

int dp[n+1][max_w+1];

int dp01bag(){
for (int i = 0; i < n; i++)
for (int j = 0; j <= max_w; j++)
if (j<w[i])
dp[i+1][j] = dp[i][j];
else
dp[i+1][j] = max(dp[i][j], dp[i][j-w[i]]+v[i]);
return dp
[max_w];
}

int main(){
int res = dp01bag();
printf("%d\n", res);
system("pause");
return 0;
}
       在这里我们使用了一个二维数组来存储中间结果,实际上我们完全可以使用一个一维的dp[max_w+1]的数组,并通过不断的重用来完成求解,从而优化内存的空间。   

#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))

const int n = 4;
const int max_w = 5;
int w
= {2, 1, 3, 2};
int v
= {3, 2, 4, 2};

int dp[max_w+1];

int dp01bag_optimize(){
for (int i = 0; i < n; i++)
for (int j = max_w; j >=w[i]; j--)
dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
return dp[max_w];
}

int main(){
int res = dp01bag_optimize();
printf("%d\n", res);
system("pause");
return 0;
}   

至此我们就完成了对01背包问题的求解。

                                                                              完全背包问题

       问题描述:有n个重量和价值分别为wi和vi的物品,从这些物品中挑选出总重量不超过max_w的物品,求所有挑选方案中价值总和的最大值。在这里,每种物品可以被挑选多次。例如输入 n = 3;  max_w = 7;  w
= {3,4,2};  v
= {4,5,3};  输出应为10(选择0号物品1个, 2号物品2个)。

       基于01背包的求解,我们做出如下假设:假设用dp(i, j)表示从前i件物品中挑选出总重量不超过j的最大价值,对于第i+1件物品我们有两种选择情况:

        1)在最优方案dp(i+1, j)中选择了第i+1件物品,假设该件物品被挑选k次(0<k<max_w),此时dp(i+1, j) = dp(i, j-k*w[i]) + k*v[i];

        2)在最优方案dp(i+1, j)中没有选择第i+1件物品,此时dp(i+1, j) = dp(i, j);

       和01背包相比,最棘手的是多了一个k,如果使用一个三重循环的话时间复杂度为O(n*max_w^2)。那么我们是否可以不使用这个k值,基于此思路我们可以获得如下的思路:  假设用dp(i, j)表示从前i件物品中挑选出总重量不超过j的最大价值,对于第i+1件物品是否被选中有两种情况:
        1)在最优方案dp(i+1, j)中选择了第i+1件物品,此时dp(i+1, j) = dp(i+1, j-w[i]) + v[i],在这里我们并不用k来标记次数,而是将第i+1件物品仍然留下,以作下次循环;

        2)在最优方案dp(i+1, j)中没有选择第i+1件物品,此时dp(i+1, j) = dp(i, j);

       于是我们得到了问题的解:dp(i+1, j) =      dp(i, j)   if w[i] > j;  第i+1件物品的总重量超过了j时,我们无法选择该物品

                                                                    max(dp(i, j), dp(i+1, j) = dp(i+1, j-w[i]) + v[i])   if w[i] <= j;

        利用自底向上的方法求解:

#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))

const int n = 3;
const int max_w = 7;
int w
= {3,4,2};
int v
= {4,5,3};

int dp[n+1][max_w+1];

int dpbag(){
for (int i = 0; i < n; i++)
for (int j = 0; j <= max_w; j++)
if (j<w[i])
dp[i+1][j] = dp[i][j];
else
dp[i+1][j] = max(dp[i][j], dp[i+1][j-w[i]]+v[i]);
return dp
[max_w];
}

int main(){
int res = dpbag();
printf("%d\n", res);
system("pause");
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息