算法学习之动态规划(一)

动态规划的两个条件是最优子结构和子问题重叠。
最优子结构:一个问题的最优解包含其子问题的最优解。换句话说,用子问题的最优解可以构造原问题的最优解。例如钢条切割问题。
子问题重叠:子问题空间必须足够小,问题的递归算法会反复地求解相同的子问题。如背包问题。

动态规划求解时的方法:带备忘的自顶向下法自底向上法
我理解的带备忘的自顶向下法就是比如需要的是子问题的最小值,然后在存储结果的数组中先赋成无穷大,然后在从上到下递归时,若没有这个值,就采取递归来计算它(通常需要遍历所有的情况取最小值);若这个值已经在数组中保存,就直接利用它。“备忘”也就是这个存储之前计算结果的数组。
个人认为自底向上法更好理解一些,即从最小的子问题出发,一步一步将所有的子问题都求解,存在数组中,最后利用这些已经求解的子问题计算出原问题的最优解。

步骤:
1.刻画一个最优解的结构特征。通常也就是将父问题进行划分,看是否满足最优子结构。
2.递归定义最优解的值。一般是将递归式写出来,将m[i,j]用m[i-1,j]或另外的参数之类的表示出来,可能还要分i==j和i < j的条件。
3.用递归式计算最优解的值。
4.用上面的方法构造一个最优解。
钢条切割问题:给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,……,n),求钢条切割方案,使得销售收益rn最大。注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。

首先,我们需要确定这个问题满足最优子结构。让我们来这样想:将钢条从左边切割下一段长度为i的一段,剩下右边长度为n-i的一段可以递归来进行切割。也就是对左边进行长度为0-n的遍历,选取最大的,即为最优解。
递归式如下:
rn = max{pi + r(n-i)}(1<=i<=n)

算法:
1.自底向上法(迭代) 用r[0..n]保存收最优收益,用s[0..n]来保存第一段切割长度

r[0] = 0
for j = 1 to n
    q = -∞
    for i = 1 to j
        //q = max{q, pi+r[j-i]}
        if q < pi+r[j-i]
            q = pi+r[j-i]
            s[j]=i  //用来保存第一段切割长度
    r[j] = q

2.带备忘的自顶向下法 (递归)

r[0] = 0
for i = 1 to n
    r[i] = -∞
f(p,n,r)

f(p,n,r)
if r[n] >= 0
    return r[n]
if n == 0
    q = 0
else
    q = -∞
    for i = 1 to n
        if q < pi+f(p,n-i,r)
            q = pi+f(p,n-i,r)
            s[j] = i
r[n] = q
return q

具体C代码如下:

#include<stdio.h>
#define M 1000
int p[11] = {0,1,5,8,9,10,17,17,20,24,30};
int r[M] = {0};
int s[M] = {0};
int BOTTOM_UP_CUT_ROD(int n)//自底向上法
{
    int i,j,q = 0;
    for(i = 0; i <= n; i++){
        r[i] = -1;//负无穷大
        s[i] = 0;
    }
    r[0] = 0;
    for(j = 1; j <= n; j++){
        q = -1;//负无穷大
        for(i = 1; i <= j; i++){
            if(q < p[i]+r[j-i]){
                q = p[i]+r[j-i];
                s[j] = i;
            }
        }
        r[j] = q;
    }
}
int MEMOIZED_CUT_ROD(int n)//带备忘的自顶向下法
{
    int i,j;
    for(i = 0; i <= n; i++){
        r[i] = -1;//负无穷大
        s[i] = 0;
    }
    return MEMOIZED_CUT_ROD_AUX(n);
}
int MEMOIZED_CUT_ROD_AUX(int n)
{
    int i,q;
    if(r[n] >= 0)
        return r[n];
    if(n == 0)
        q = 0;
    else{
        q = -1;//负无穷大
        for(i = 1; i <= n; i++){
            if(q < p[i]+MEMOIZED_CUT_ROD_AUX(n-i)){
                q = p[i] + MEMOIZED_CUT_ROD_AUX(n-i);
                s[n] = i;
            }
        }
    }
    r[n] = q;
    return q;
}
void print(int n)
{
    printf("对应的切割方案为:");
    while(n > 0){
        printf("%d ",s[n]);
        n = n - s[n];
    }
    printf("\n");
}
int main()
{
    int n,k;
    printf("请输入钢条长度:");
    scanf("%d",&n);
    BOTTOM_UP_CUT_ROD(10);
    printf("自底向上:%d\n",r[n]);
    print(n);
    printf("自顶向下:%d\n",MEMOIZED_CUT_ROD(n));
    print(n);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值