《算法导论》在对动态规划讲解时,第一个问题就是钢条切割的问题。
在书中,对这个问题提供了3种思路

  1. 利用普通的递归来解,时间复杂度为O(2^n)
  2. 对普通的递归进行优化,使其带有记忆功能,减少运行时间。即自顶向下的动态规划
  3. 运用数组,自底向上的动态规划

现在依次讲解这三个思路,并且用代码实现它们!

首先先看题


现在给你一根钢条,你可以把它切成几个小部分(也可以不用切割),要找到一种方法,使得这根钢条可以卖出最多的价钱。

如:长度为4的钢条有三种卖法

  1. 不切割直接买,可以买价格9
  2. 切割为1,3再买,同样是价格9
  3. 切割为2,2再买,价格为10

可以看出,把4分为2,2来买可以达到最大收益。

普通递归方法

在上面的例子中,一根钢条可以被分为很多份,可以在被分的这些小份中继续分解,来找到最优解。这和递归的思路非常相似——将一个大问题分解子问题来求解。
首先假设一个问题的子问题的解都是最优的,于是,对于一个长度为L的钢条,若把他分为两段,L的最优解可能为 k,L-k (k=0,1,2,…,L-1)
现在遍历k的值,就可以找出最大的解(默认k,L-k为这个长度的最优解)
可以知道,虽然现在不知道k,L-k 是否为最优解,但是整个过程是递归的,在递归计算中可以知道k,L-k的最优解。

普通递归代码实现
创建两个大小为11的数组来储存这些钢条能卖出的价格,和当前钢条能买到的最大价格

int N[11]={0,1,5,8,9,10,17,17,20,24,30};
int r[11]={0};

编写递归函数

int re(int n)
{if(n==0)  //钢条长度为零的时候,返回零return 0;int q=N[n];int i;for(i=1;i<n;i++)  //遍历k~L-k每一种情况,找到里面的最大值,k为零时为不切割的情况,q=N[n]已经储存,不需要考虑q=maxx(q,r[i]+re(n-i));r[n]=q; //钢条长度为n时最多可以买到价格qreturn q;
}

完整代码

#include<stdio.h>
#include<time.h>
//int N[44]={0,1,5,8,9,10,17,17,20,24,30,0,1,5,8,9,10,17,17,20,24,30,0,1,5,8,9,10,17,17,20,24,30,0,1,5,8,9,10,17,17,20,24,30};
int N[11]={0,1,5,8,9,10,17,17,20,24,30};
int r[11]={0};
int re(int);
int maxx(int ,int);
int main()
{int current;puts("输入你要裁剪的钢条长度:");scanf("%d",&current);clock_t start, finish;start=clock();int i;printf("%d长的钢条最大收益是:%d\n",current,re(current));printf("各个长度的收益情况:\n长度:");for(i=0;i<=current;i++){printf("%3d ",i);}puts("");printf("收益:");for(i=0;i<=current;i++){printf("%3d ",r[i]);}puts("");finish=clock();double Total_time = (double)(finish-start) / CLOCKS_PER_SEC;printf("%f 秒\n",Total_time);scanf("%d",&current);
}
int re(int n)
{if(n==0)return 0;int q=N[n];int i;for(i=1;i<n;i++)q=maxx(q,r[i]+re(n-i));r[n]=q;return q;
}
int maxx(int a,int b)
{if(a>=b)return a;elsereturn b;
}

可以扩大数组N的大小,使其可以测试更多的钢条。
我测试了一下他们的时间

  1. n为31时运行时间为5s
  2. n为32时运行时间为10s
  3. n为33时运行时间为20s

从这里也可以看出,普通递归的时间复杂度为O(2^n),是指数的。

自顶向下的动态规划法

观察普通的递归方法可以知道,创建的数组r除了最后打印收益结果时发挥了作用,并没有发挥其他的功能。并且仔细调试普通递归方法可以发现,它重复多次的计算了许多子问题。比如当n=4时,普通递归运行的方式是


图片来源:https://zhuanlan.zhihu.com/p/70763958
重复的计算这些子问题是浪费时间的,并且随着钢条长度的增加,这些时间会指数增长。
现在,让数组r来储存这些子问题的数据,不在重复计算子问题。

修改后的递归代码

int upToDown(int n) //每次返回的值是当前长度n的最大收益
{if(r[n]!=0)return r[n];if(n==0)return 0;int q=N[n];    int i;for(i=1;i<n;i++)q=maxx(q,r[i]+upToDown(n-i));r[n]=q;return q;
}

所添加的代码

if(r[n]!=0)return r[n];

由于r的初始值为零,r[n]不为零的时候,就代表子问题n已经被处理过,r[n]就是钢条长度的最大收益,可以直接返回,不需要继续递归
完整代码

#include<stdio.h>
int N[11]={0,1,5,8,9,10,17,17,20,24,30};
int upToDown(int);
int r[11]={0};
int maxx(int a,int b);
int main()
{int current;puts("输入你要裁剪的钢条长度:");scanf("%d",&current);int i;printf("%d长的钢条最大收益是:%d\n",current,upToDown(current));printf("各个长度的收益情况:\n长度:");for(i=0;i<=current;i++){printf("%3d ",i);}puts("");printf("收益:");for(i=0;i<=current;i++){printf("%3d ",r[i]);}
}
int upToDown(int n) //即每次返回的值是当前长度n的最大收益
{if(r[n]!=0)return r[n];if(n==0)return 0;int q=N[n];    int i;for(i=1;i<n;i++)q=maxx(q,r[i]+upToDown(n-i));r[n]=q;return q;
}
int maxx(int a,int b)
{if(a>=b)return a;elsereturn b;
}

个人看法,若要求长度n的最大收益,就必须知道他的子问题的收益,如子问题收益已知就直接返回,如果不知道,就继续求解子问题的子问题。从这个思路可以看出,只有知道了子问题才可以求出当前问题,所以虽然是从n开始递归,但最终都是求出了最基本的子问题之后,再开始往上求解上一级的子问题。所以自顶向下也是有一个自底向上的过程。

自底向上的动态规划

由于最终都是要找到最底层,最基本的子问题,所以干脆直接从最底层开始自底向上的求出最终解。
同样创建两个大小为11的数组来储存这些钢条能卖出的价格,和当前钢条能买到的最大价格

int N[11]={0,1,5,8,9,10,17,17,20,24,30};
int n[11]={0};

动态规划的核心是保存已计算的子问题的值,并且是自底向上的动态规划,我们选用嵌套的两个for循环,从长度1开始依次计算各个长度的最优解。
核心代码:

 puts("输入你要裁剪的钢条长度:");scanf("%d",&current);for(i=1;i<=current;i++){int q=N[i];for(j=1;j<=i;j++){q=maxx(q,n[j]+n[i-j]);}n[i]=q;}

内层循环

for(j=1;j<=i;j++)
{q=maxx(q,n[j]+n[i-j]);
}

每一次内层循环之后,长度为i的最优解就已知了。

在钢条长度为i时,遍历i的各种切割方法,来求出长度i的最优解
仔细思考内外层循环可知

  1. i=1时,内层循环运行1次
  2. i=2时,内层循环运行2次
  3. i=3时,内层循环运行3次

i为n时,内层循环运行n次。
所以总的迭代次数构成一个公差为1的等差数列,显然时间复杂度为O(n^2).

总结

利用动态规划,完成了时间复杂度从指数到常数时间的优化,足以看出动态规划的巨大威力,能用动态规划运用于求解有重复子问题的情况,动态规划不会重复计算子问题,从而大大节约了时间。

摘一段《算法导论》中的话

我通常按照4个步骤来设计一个动态规划算法
1.刻画一个最优解的特征
2.递归的定义最优值
3.计算最优值,通常采用自底向上的方法

从第2点可以看出,动态规划和递归之间有着千丝万缕的联系,动态规划可以利用递归的思路非递归的来解决问题,从而到优化时间的目的!

算法导论——钢条切割问题(C语言)相关推荐

  1. 算法导论---钢条切割

    题目: Serling公司购买长钢条,将其切割为短钢条出售.切割工序本身没有成本支出.公司管理层希望知道最佳的切割方案. ​ 假定我们知道Serling公司出售一段长度为i英寸的钢条的价格为p;(i= ...

  2. 《算法导论》学习(十七)----动态规划之钢条切割(C语言)

    文章目录 前言 一.钢条切割问题 1.问题背景 2.问题描述 3.问题的难点 (1)情况较多 (2)消除重复子问题 二.问题解决方案 1.问题的特点 (1)最优化子结构 (2)重复子问题 2.最优化解 ...

  3. 算法导论 动态规划钢条切割问题 C语言

    动态规划钢条切割问题 动态规划(dynamic programming)与分治法类似.分治策略将问题划分为互不相交的子问题,递归求解子问题,再将子问题进行组合,求解原问题.动态规划应用于子问题重叠的情 ...

  4. 算法导论动态规划切割钢条

    保存已经求得的子问题解 自底向上 #ifndef _MODEL_ #define _MODEL_ #include<vector> #include<map> using st ...

  5. 算法导论—分治策略(C语言)

    在分治策略中,我们递归的求解一个问题,在每层递归中应用以下三个步骤: 1.分解   将问题划分为一个个子问题,子问题形式与原问题一致,只是规模更小 2.解决   这里的解决是指递归的求解出子问题,或对 ...

  6. 《算法导论》中动态规划求解钢条切割问题

    动态规划算法概述 动态规划(dynamic programming)1是一种与分治方法很像的方法,都是通过组合子问题的解来求解原问题.不同之处在于,动态规划用于子问题重叠的情况,比如我们学过的斐波那契 ...

  7. 算法导论-动态规划(钢条切割问题)

    写下文章来记录下自己学习算法导论的笔记 文章目录 写下文章来记录下自己学习算法导论的笔记 动态规划的目的 设计动态规划算法 钢条切割问题 问题描述 刻画问题结构(建立方程) 递归方程建立 带备忘录的自 ...

  8. 《算法导论》15章-动态规划 15.1 钢条切割(含有C++代码)

    一.引入 动态规划方法通常用来求解最优化问题(optimizationproblem).这类问题可以有很多可行解,每个解都有一个值,我们希望寻找具有最优值(最小值或最大值)的解.我们称这样的解为问 题 ...

  9. 算法导论15.1动态规划之钢条切割

    动态规划与钢条切割 1.分治算法与动态规划 相同点: 都是通过组合子问题的解来求解原问题 不同: 1.分治将问题划分为互不相交的子问题,递归地求解子问题,在将它们的解组合起来,求出原问题. 2.动态规 ...

最新文章

  1. 计算机视觉方向简介 | 阵列相机立体全景拼接
  2. 工作流引擎 Activiti 实战系列
  3. nt是linux指令吗,linux shell 指令 诸如-d, -f, -e之类的判断表达式简介
  4. 伟世盾安助国电高级培训中心实现一网双管
  5. VTK修炼之道51:图形基本操作进阶_连通区域分析
  6. 周末也需要学习 分享一个 Flutter 波浪波动效果的登录页面的背景 Flutter ClipPath实现的波动
  7. 关于DataV大屏分辨率那些事
  8. DevExpress下拉多选框 CheckComboboxEdit、CheckedListBoxControl
  9. PHP API微信网页授权接口实现
  10. 06_注册时密码加密
  11. 腾讯微博开放平台OAuth1.0授权完整流程(C#)
  12. win8计算机里没有用户名和密码错误,win8电脑其他用户的用户名和密码肿么弄?
  13. 龙芯2f笔记本- openbsd5.4安装手记
  14. 龙贝格积分——matlab实现
  15. 绘制logo软件-AI(illustrator)
  16. JavaScript点击图片提示
  17. 迭代器 iter()
  18. 谷歌浏览器表格无法导入_如何从另一个Google表格导入数据
  19. 【hardware】什么是H桥电路?
  20. Bernoulli分布的特征函数及期望与方差

热门文章

  1. 2022年二级建造师建设工程法规及相关知识精选试题及答案
  2. SpringCloud(2)--服务调用
  3. Modularity and community structure in networks
  4. 医疗人工智能前景——医学影像
  5. Axure RP 9 基础教程 元件基础1
  6. Dining (匹配,最大流)
  7. 实现轮播模拟点击事件
  8. Java+spring+vue基于ssm的农家乐预定管理系统
  9. 我如何转变了我的YouTube推荐供稿
  10. [干货] 一文看懂numpy.nonzero() 与 numpy.argwhere()非零元素处理