算法导论——钢条切割问题(C语言)
《算法导论》在对动态规划讲解时,第一个问题就是钢条切割的问题。
在书中,对这个问题提供了3种思路
- 利用普通的递归来解,时间复杂度为O(2^n)
- 对普通的递归进行优化,使其带有记忆功能,减少运行时间。即自顶向下的动态规划
- 运用数组,自底向上的动态规划
现在依次讲解这三个思路,并且用代码实现它们!
首先先看题
现在给你一根钢条,你可以把它切成几个小部分(也可以不用切割),要找到一种方法,使得这根钢条可以卖出最多的价钱。
如:长度为4的钢条有三种卖法
- 不切割直接买,可以买价格9
- 切割为1,3再买,同样是价格9
- 切割为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",¤t);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",¤t);
}
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的大小,使其可以测试更多的钢条。
我测试了一下他们的时间
- n为31时运行时间为5s
- n为32时运行时间为10s
- 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",¤t);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",¤t);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的最优解
仔细思考内外层循环可知
- i=1时,内层循环运行1次
- i=2时,内层循环运行2次
- i=3时,内层循环运行3次
i为n时,内层循环运行n次。
所以总的迭代次数构成一个公差为1的等差数列,显然时间复杂度为O(n^2).
总结
利用动态规划,完成了时间复杂度从指数到常数时间的优化,足以看出动态规划的巨大威力,能用动态规划运用于求解有重复子问题的情况,动态规划不会重复计算子问题,从而大大节约了时间。
摘一段《算法导论》中的话
我通常按照4个步骤来设计一个动态规划算法
1.刻画一个最优解的特征
2.递归的定义最优值
3.计算最优值,通常采用自底向上的方法
从第2点可以看出,动态规划和递归之间有着千丝万缕的联系,动态规划可以利用递归的思路非递归的来解决问题,从而到优化时间的目的!
算法导论——钢条切割问题(C语言)相关推荐
- 算法导论---钢条切割
题目: Serling公司购买长钢条,将其切割为短钢条出售.切割工序本身没有成本支出.公司管理层希望知道最佳的切割方案. 假定我们知道Serling公司出售一段长度为i英寸的钢条的价格为p;(i= ...
- 《算法导论》学习(十七)----动态规划之钢条切割(C语言)
文章目录 前言 一.钢条切割问题 1.问题背景 2.问题描述 3.问题的难点 (1)情况较多 (2)消除重复子问题 二.问题解决方案 1.问题的特点 (1)最优化子结构 (2)重复子问题 2.最优化解 ...
- 算法导论 动态规划钢条切割问题 C语言
动态规划钢条切割问题 动态规划(dynamic programming)与分治法类似.分治策略将问题划分为互不相交的子问题,递归求解子问题,再将子问题进行组合,求解原问题.动态规划应用于子问题重叠的情 ...
- 算法导论动态规划切割钢条
保存已经求得的子问题解 自底向上 #ifndef _MODEL_ #define _MODEL_ #include<vector> #include<map> using st ...
- 算法导论—分治策略(C语言)
在分治策略中,我们递归的求解一个问题,在每层递归中应用以下三个步骤: 1.分解 将问题划分为一个个子问题,子问题形式与原问题一致,只是规模更小 2.解决 这里的解决是指递归的求解出子问题,或对 ...
- 《算法导论》中动态规划求解钢条切割问题
动态规划算法概述 动态规划(dynamic programming)1是一种与分治方法很像的方法,都是通过组合子问题的解来求解原问题.不同之处在于,动态规划用于子问题重叠的情况,比如我们学过的斐波那契 ...
- 算法导论-动态规划(钢条切割问题)
写下文章来记录下自己学习算法导论的笔记 文章目录 写下文章来记录下自己学习算法导论的笔记 动态规划的目的 设计动态规划算法 钢条切割问题 问题描述 刻画问题结构(建立方程) 递归方程建立 带备忘录的自 ...
- 《算法导论》15章-动态规划 15.1 钢条切割(含有C++代码)
一.引入 动态规划方法通常用来求解最优化问题(optimizationproblem).这类问题可以有很多可行解,每个解都有一个值,我们希望寻找具有最优值(最小值或最大值)的解.我们称这样的解为问 题 ...
- 算法导论15.1动态规划之钢条切割
动态规划与钢条切割 1.分治算法与动态规划 相同点: 都是通过组合子问题的解来求解原问题 不同: 1.分治将问题划分为互不相交的子问题,递归地求解子问题,在将它们的解组合起来,求出原问题. 2.动态规 ...
最新文章
- 计算机视觉方向简介 | 阵列相机立体全景拼接
- 工作流引擎 Activiti 实战系列
- nt是linux指令吗,linux shell 指令 诸如-d, -f, -e之类的判断表达式简介
- 伟世盾安助国电高级培训中心实现一网双管
- VTK修炼之道51:图形基本操作进阶_连通区域分析
- 周末也需要学习 分享一个 Flutter 波浪波动效果的登录页面的背景 Flutter ClipPath实现的波动
- 关于DataV大屏分辨率那些事
- DevExpress下拉多选框 CheckComboboxEdit、CheckedListBoxControl
- PHP API微信网页授权接口实现
- 06_注册时密码加密
- 腾讯微博开放平台OAuth1.0授权完整流程(C#)
- win8计算机里没有用户名和密码错误,win8电脑其他用户的用户名和密码肿么弄?
- 龙芯2f笔记本- openbsd5.4安装手记
- 龙贝格积分——matlab实现
- 绘制logo软件-AI(illustrator)
- JavaScript点击图片提示
- 迭代器 iter()
- 谷歌浏览器表格无法导入_如何从另一个Google表格导入数据
- 【hardware】什么是H桥电路?
- Bernoulli分布的特征函数及期望与方差
热门文章
- 2022年二级建造师建设工程法规及相关知识精选试题及答案
- SpringCloud(2)--服务调用
- Modularity and community structure in networks
- 医疗人工智能前景——医学影像
- Axure RP 9 基础教程 元件基础1
- Dining (匹配,最大流)
- 实现轮播模拟点击事件
- Java+spring+vue基于ssm的农家乐预定管理系统
- 我如何转变了我的YouTube推荐供稿
- [干货] 一文看懂numpy.nonzero() 与 numpy.argwhere()非零元素处理