算法导论--动态规划(钢条切割)

本文详细阐述了利用动态规划解决钢条切割问题的方法,包括子问题分解、递归调用、备忘录优化及自底向上求解策略。通过实例演示,展示了如何通过动态规划算法最大化收益,同时提供了保存切割方法的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

钢条切割问题

现有一段长度为n英寸的钢条和一个价格表pi,求切割方案使销售利益最大rn最大
这里写图片描述

长度为n英寸的钢条共有2n1种不同的切割方案,因为可以每个整英寸的位置都可以决定切割或者不切割。

  • 为了得到rn最大,可以把这个问题分成子问题求解,先切一刀,再考虑余下的部分的最大收益即求
    rn=max{pk+rnk}(k=1,2,3…n-1),
    pk部分不进行继续切割,直接作为一个整体售出 ;
    rnk部分继续切割,考虑所有的情况,分成子问题。
    求出所有k值对应的收益最大者作为rn

  • 也有可能不进行任何切割利益最大即rn=pn

    综上rn=max(pi+rni) (i=1,2,3…n , r0=0)

动态规划求解

考虑上面的算法实现:rn=max(pi+rni) (i=1,2,3…n , r0=0),为了计算出rn必须计算出所有情况的切割方法,即有n个分支,第i个分支又对应着i个子分支,所以若采用递归调用计算所有情况,调用次数将是2n次。运行时间为n的指数函数。

上面的分析知,其中重复计算了很多次子问题,如果有个好的计算顺序,子问题先计算一次,把结果保存下来,下次再遇到这个子问题,只需要查表即可。动态规划方法是付出额外的内存空间来节省计算时间。

带备忘的自顶向下法

仍采用递归的方法,但过程中保存已求解过子问题的解,当需要一个子问题的解时,首先检查是否已经保存过此解。

int Memoized_Cut_Rod(int *p,int n)
{   
    int i=0;
    int * r = (int *)malloc((n+1)*sizeof(int));  //分配空间,用来存储已经计算过的子问题
    for (i=0;i<=n;i++)                          //初始化
        r[i] = -1;
    return Memoized_Cut_Rod_Aux(p,n,r);         
}
int Memoized_Cut_Rod_Aux(int *p,int n,int *r)
{
    int q= -1,i;
    if (r[n] >=0)   //首先检查是否已经计算过,若有,直接返回
        return r[n];
    if (n == 0)     //钢条长度为0,收益为0
        q=0;
    else
    {
        q = -1;
        for (i=1;i<=n;i++) 
        {
           q=Max(q,p[i]+Memoized_Cut_Rod_Aux(p,n-i,r));   //自顶向下递归
        }
        r[n] = q;        //保存子问题的值
    }

    return q;        //返回最大的收益
}

自底向上法

把子问题按规模排序,按由小到大的顺序进行求解,当求解某个问题时,它所依赖的子问题已经被计算过,结果已经保存,这样每个子问题只需要计算一次。

int Bottom_Up_Cut_Rod(int *p,int n)
{
int * r = (int *)malloc((n+1)*sizeof(int));   //分配空间,用来存储已经计算过的子问题
int i,j,q=-1;
  r[0]=0;
  for (j=1;j<=n;j++)
    {
       q=-1;
       for(i=1;i<=j;i++)
       {
           q=Max(q,p[i]+r[j-i]);   //自底向上,由小到大求解子问题
       }
       r[j]=q;   //保存求解后的结果
    }
  return r[n];
}

重构解

上面的代码是返回的最大收益,并没有返回具体切割方法。下面代码保存了每种尺寸,对应的左边部分大小(即作为整体售出部分)

int Extended_Bottom_Up_Cut_Rod(int *p,int n)
{
    int * r = (int *)malloc((n+1)*sizeof(int));   
    int i,j,q=-1;
    s = (int *)malloc((n+1)*sizeof(int));  //保存每次切割前左边部分剩余尺寸
    s[0]=0;
    r[0]=0;
    for (j=1;j<=n;j++)
    {
        q=-1;
        for(i=1;i<=j;i++)
        {

            if (p[i]+r[j-i] > q)
            {
                s[j] = i;      //保存左边部分(作为整体售出的部分)
                q = p[i] + r[j-i];
            }
        }
        r[j]=q;
    }

    return r[n];
}

输入钢条的具体切割方法:

void Print_Cut_Rod_Solution(int *p,int n)
{
    while(n>0)
    {
        printf("%d   ",s[n]);   //循环输出尺寸为n的钢条切割方法
        n=n-s[n];
    }
}

例程

/************************************************************************
优快云 勿在浮沙筑高台 
http://blog.youkuaiyun.com/luoshixian099
算法导论--动态规划(钢条切割)
2015年6月2日                    
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
int *s=NULL;
int Memoized_Cut_Rod_Aux(int *p,int n,int *r);
int Max(int a,int b)
{
    return a>b?a:b;
}
int Memoized_Cut_Rod(int *p,int n)
{   
    int i=0;
    int * r = (int *)malloc((n+1)*sizeof(int));  //分配空间,用来存储已经计算过的子问题
    for (i=0;i<=n;i++)                          //初始化
        r[i] = -1;
    return Memoized_Cut_Rod_Aux(p,n,r);         
}
int Memoized_Cut_Rod_Aux(int *p,int n,int *r)
{
    int q= -1,i;
    if (r[n] >=0)   //首先检查是否已经计算过,若有,直接返回
        return r[n];
    if (n == 0)     //钢条长度为0,收益为0
        q=0;
    else
    {
        q = -1;
        for (i=1;i<=n;i++)
        {
           q=Max(q,p[i]+Memoized_Cut_Rod_Aux(p,n-i,r));
        }
        r[n] = q;        //保存子问题的值
    }

    return q;        //返回最大的收益
}
int Bottom_Up_Cut_Rod(int *p,int n)
{
int * r = (int *)malloc((n+1)*sizeof(int));   //分配空间,用来存储已经计算过的子问题
int i,j,q=-1;
  r[0]=0;
  for (j=1;j<=n;j++)
    {
       q=-1;
       for(i=1;i<=j;i++)
       {
           q=Max(q,p[i]+r[j-i]);   //自底向上,由小到大求解子问题
       }
       r[j]=q;   //保存求解后的结果
    }
  return r[n];
}

int Extended_Bottom_Up_Cut_Rod(int *p,int n)
{
    int * r = (int *)malloc((n+1)*sizeof(int));   
    int i,j,q=-1;
    s = (int *)malloc((n+1)*sizeof(int));  //保存每次切割前左边部分剩余尺寸
    s[0]=0;
    r[0]=0;
    for (j=1;j<=n;j++)
    {
        q=-1;
        for(i=1;i<=j;i++)
        {

            if (p[i]+r[j-i] > q)
            {
                s[j] = i;      //保存左边部分(作为整体售出的部分)
                q = p[i] + r[j-i];
            }
        }
        r[j]=q;
    }

    return r[n];
}
void Print_Cut_Rod_Solution(int *p,int n)
{

    while(n>0)
    {
        printf("%d   ",s[n]);   //循环输出尺寸为n的钢条切割方法
        n=n-s[n];
    }
}
void main()
{
   int p[]={0,1,5,8,9,10,17,17,20,24,30};
   int i;
   for (i=1;i<=10;i++)
   {
     //printf("%d\n",Memoized_Cut_Rod(p,i));
     printf("尺寸为%d的最大收益:%d ",i,Extended_Bottom_Up_Cut_Rod(p,i));
    // printf("%d  \n",s[i]);
     printf("切割方法:");
     Print_Cut_Rod_Solution(p,i);
     free(s);
     s=NULL;
     printf("\n");
   }
}

这里写图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值