分治与动态规划(3种背包问题)

动态规划、分治法和贪心法都是利用求解子问题,而后利用子问题求解更上层问题,最终获得全局解决方案的方法。但是三者的应用场景和性质却存在着极大的不同:

1. 分治法

分治法的精髓:

分–将问题分解为规模更小的子问题;
治–将这些规模更小的子问题逐个击破;
合–将已解决的子问题合并,最终得出“母”问题的解;

很容易将分治法与动态规划相混淆,但两者却有着本质上的差异。分治法采用的是递归的思想来求解问题,两个分解的子问题独立求解,其之间无任何的重叠。而上一层问题只需要对两个子问题进行一定的合并即可得到答案。即s(t)= s(sub1)+s(sub2),而s(sub1)s(sub2)之间无任何重叠。

1.1 使用分治法求数组中的最大值

函数将数组a[1],……,a[r]分成两部分,分别求出每部分的最大元素(递归地),并返回较大的那一个作为整个数组的最大元素。

int a[];
...

int max(int l,int r) {
    if (l==r) return a[l];//如果只有一个元素,就返回它

    int m=(l+r)/2;
    int u = max(l,m);
    int v = max(m+1,r);

    return (u>v) ? u : v;
}

2. 动态规划

该种方法较为复杂,但十分有用和高效,其核心性质是当前问题的答案s(t)并不能单独由s(t-1)求得,还有可能需要使用到s(1)...s(t-1)。具体需要使用到那些,是由问题本身的性质所决定的(常常是一个约束,或变相的约束)。
动态规划分解后的子问题相互间有联系,有重叠的部分。

2.1 01背包问题

01背包问题:
一个背包总共可以装下重量为W的东西,现在有N个物品,第i个物品的重量为weight[i],其价值为value[i],现在往背包里面装东西,希望能够使得背包装下的物品的总价值最大。
DP要求先找出子问题,我们可以这样考虑:在物品比较少,背包容量比较小时怎么解决?用一个数组dp[i][j]表示,在只有i个物品,容量为j的情况下背包问题的最优解,那么当物品种类变大为i+1时,最优解是什么?第i+1个物品可以选择放进背包或者不放进背包(这也就是0和1),假设放进背包(前提是放得下),那么dp[i+1][j]=dp[i][j-weight[i+1]]+value[i+1];如果不放进背包,那么f[i+1][j]=f[i][j]
这就得出了状态转移方程:
f[i+1][j]=max(f[i][j],f[i][j-weight[i+1]+value[i+1])
完整代码:

#define V 1500
int dp[10][V];//全局变量,自动初始化为0
int weight[10];
int value[10];
#define max(x,y) (x)>(y)?(x):(y)
int main() {
    int N,M;
    cin>>N;//物品个数
    cin>>M;//背包容量
    for (int i=1;i<=N; i++) {
        cin>>weight[i]>>value[i];
    }
    for (int i=1; i<=N; i++) 
        for (int j=1; j<=M; j++) {
            if (weight[i]<=j) 
                dp[i][j] = max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
            else
                dp[i][j] = dp[i-1][j];
        }

    cout<<dp[N][M]<<endl;

}

可以将dp[i][j]这个二维数组优化成一维数组。
另外,还有多重背包和完全背包问题,可以见链接

优化,以及01背包和完全背包问题的区别

有趣的是,当我们将二维数组优化成一维数组时,01背包和完全背包问题的解法几乎完全一样,只有一句需要改变。
01背包的解:

int main() {
    int n,m;
    while(cin>>n>>m) {
        vector<int> weight(n+1,0);//物品的重量
        vector<int> value(n+1,0);//物品的价值
        vector<int> dp(m+1,0);//一维数组,存放用n个产品装容量为i的背包的解
        for(int i=0;i<n;i++)
            cin>>weight[i+1]>>value[i+1];//输入

        for(int i=1;i<=n;i++)
            //这里是逆序
            //用j>=weight[i]作为判断条件,省略了一个if语句
            for(int j=m;j>=weight[i];j--) {
                    dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);

        cout<<dp[m]<<endl;
    }
    return 0;
}

这里,只用到了一维数组,但是通过采用逆序的方法,使得效果是相同的。

然后是完全背包问题的解,只需要将上面的逆序改为顺序:

for(int i=1;i<=n;i++)
        //这里变成了顺序
        //这里设j从weight[i]开始,省略了一个if语句
        for(int j=weight[i];j<=m;j++)
                dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
    原文作者:动态规划
    原文地址: https://blog.csdn.net/jinzhao1993/article/details/50410329
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞