《编程原本 》一3.3 程序变换
3.3 程序变换
power0是有关算法的一个令人满意的实现,它适用于运算的代价高于函数递归调用开销的情况.本节要推导出一个迭代算法,它执行运算的次数和power0一样.这里将要做一系列程序变换,这些变换也可以用在其他许多情况中.5 在本书后面的部分,通常将只给出算法的最终版本或几乎最终版本.
power0包含两个相同的递归调用,它每次只执行其中一个.这使我们可能通过公共子表达式删除技术来缩小代码的规模:
template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power 1(Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
if (n == I(1)) return a;Domain(Op) r = power 1(op(a, a), n / I(2), op);if (n % I(2) != I(0)) r = op(r, a);return r;
}
现在的目标是删除递归调用,为此要做的第一步是把过程变换到尾递归形式(tail-recursiveform),其中在过程执行的最后一步是对自身的递归调用.完成该变换的一种技术是引入累积变量(accumulation-variableintroduction),用于在不同递归调用之间携带累积的结果:
template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 0(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n == I(0)) return r;
5.只有在运算的语义和复杂性已知的情况下,编译器才会对一些内部类型做类似变换.规范性概念是类型创建者的一个断言,它保证程序员和编译器可以安全地执行这些变换.
if (n % I(2) != I(0)) r = op(r, a);
return power accumulate 0(r, op(a, a), n / I(2), op); }
设r0,a0和n0是r,a和n的原值,下面不变式(recursioninvariant)在每次递归调用时都成立:ran=r0an0 0 .这个版本还有另一优点,它不仅计算幂,还能计算乘以一个系数的幂.它也处理了指数为0的情况.但是在指数从1变到0时poweraccumulate0将多做一次平方.增加一种情况就可以消除它:
template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 1(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n == I(0)) return r;
if (n == I(1)) return op(r, a);
if (n % I(2) != I(0)) r = op(r, a);
return power accumulate 1(r, op(a, a), n / I(2), op); }
增加额外情况导致重复出现的子表达式,也使三个检测不独立了.通过仔细分析检测之间的依赖性和顺序,考虑它们的出现频率,可以给出
template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 2(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n % I(2) != I(0)) {
r = op(r, a);
if (n == I(1)) return r;
} else if (n == I(0)) return r; return power accumulate 2(r, op(a, a), n / I(2), op);
}
在一个尾递归过程里,如果所有递归调用中的过程形参都是对应的实参,它就是一个严格尾递归的(stricttail-recursive)过程:
template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 3(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n % I(2) != I(0)) {
r = op(r, a);
if (n == I(1)) return r;
} else if (n == I(0)) return r;
a = op(a, a);
n = n / I(2);
return power accumulate 3(r, a, n, op); }
严格尾递归过程可以变换为一个迭代过程,方法是把每个递归调用代换为一个到过程开始的goto,也可以用一个等价的迭代结构:
template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 4(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
while (true) {
if (n % I(2) != I(0)) {
r = op(r, a);
if (n == I(1)) return r; } else if (n == I(0)) return r;a = op(a, a);n = n / I(2);
} }
递归不变式变成了这里的循环不变式(loopinvariant).如果开始时n>0,在变成0前要先经过1.我们借用这种情况消去对0的检查并加强前条件(strengthening`
javascript
precondition):
template requires(Integer(I) && BinaryOperation(Op))
Domain(Op) power accumulate positive 0(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
while (true) {
if (n % I(2) != I(0)) {r = op(r, a);if (n == I(1)) return r;
}
a = op(a, a);n = n / I(2);
} }
知道了n>0会很有用.在开发组件的过程中经常会发现新的接口情况.现在放松前条件(relaxingprecondition):
template requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 5(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n == I(0)) return r;return power accumulate positive 0(r, a, n, op);
}
通过一个简单的等式,就可以用poweraccumulate实现power:
nn.1
a = aa
这一变换就是消去累积变量(accumulation-variableelimination):
template requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power 2(Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
return power accumulate 5(a, a, n -I(1), op);
}
这个算法多做了一些不必要的运算.例如,当n是16时它要执行7次运算,其中只有4次是必要的.当n是奇数时这个算法很好.避免上述问题的方法是反复做a的平方,并不断将指数折半直至它变成奇数:
template requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power 3(Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
while (n % I(2) == I(0)) {a = op(a, a);n = n / I(2);
}
n = n / I(2);if (n == I(0)) return a;
return power accumulate positive 0(a, op(a, a), n, op);
}
练习3.1 请自己确认最后三行代码是正确的.
《编程原本 》一3.3 程序变换相关推荐
- 《编程原本 》一2.1 变换
2.1 变换 虽然从任意一组类型到任意类型的函数都存在,但是有些具有特殊签名的函数类更为常用.本书中经常用的是两类函数:同源谓词和同源运算.同源谓词在形式上都是Tו••× T → bool;同源运算 ...
- c语言二维图形变换程序,C语言图形编程(五、二维图形变换-01)(国外英文资料).doc...
C语言图形编程(五.二维图形变换-01)(国外英文资料) C语言图形编程(五.二维图形变换-01)(国外英文资料) The first user coordinates to the screen c ...
- 编程开发学习笔记之程序员如何用1年时间获得3年成长(图)
2019独角兽企业重金招聘Python工程师标准>>> 编程开发学习笔记之程序员如何用1年时间获得3年成长(图) 前言 这世界存在这么一个银行,你一出生,就自动享有这家银行为你开设的 ...
- 计算机快捷键桌布,桌面改造 篇三:编程娱乐两不误 | 伪程序猿的Windows双屏组建/效率工具/桌面美化指南...
桌面改造 篇三:编程娱乐两不误 | 伪程序猿的Windows双屏组建/效率工具/桌面美化指南 2020-07-10 11:41:39 153点赞 1107收藏 74评论 哈喽大家好,我是码呆茶!作为一 ...
- 编程第一个Apple Watch程序创建项目
编程第一个Apple Watch程序创建项目 2.4 编程第一个程序 本节将通过编写第一个程序,为开发者讲解如何添加Watch应用对象.运行程序.界面设计.编写代码等内容本文选自Apple Watc ...
- [强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!)...
[强烈推荐]ORACLE PL/SQL编程详解之七: 程序包的创建与应用(聪明在于学习,天才在于积累!) --通过知识共享树立个人品牌. 继上七篇: [推荐]ORACLE P ...
- 知乎热议:27岁没文凭、想自学编程,有机会成为程序员吗?互联网风口一去不复返了吗?
缘起 前段时间在知乎里看到了一个问题,感慨万千,拿出来给大家分享分享,希望可以抛砖引玉给大家一点启发. 问题大家已经从文章的标题里看到了:一个27岁没文凭,想去努力自学编程,有机会成为程序员吗? 而问 ...
- java设计一个形状shape_编程定义一个图形接口 Shape, 内含2个抽象方法 get... JAVA编程题:编一个程序包含一个接口 shape(该接......
导航:网站首页 > 编程定义一个图形接口 Shape, 内含2个抽象方法 get... JAVA编程题:编一个程序包含一个接口 shape(该接... 编程定义一个图形接口 Shape, 内含2 ...
- python开发cs程序_CSE209代做、代写Computer Graphics、代做CS/python编程设计代写Python程序|代做Processing...
CSE209代做.代写Computer Graphics.代做CS/python编程设计代写Python程序|代做ProcessingCSE209 Computer Graphics~1~CSE209 ...
最新文章
- redis cluster 安装配置
- 如何快速评估16S rRNA基因引物的覆盖率及特异性
- 计算机在生物科学领域的应用论文,大学生物科学教学中计算机的应用
- php 大牛生小牛,C#算法之关于大牛生小牛的问题
- 【机器学习】异常检测算法速览(Python代码)
- 1G服务器网站,1核1g内存云服务器建网站
- linux 两个mysql_Linux 安装两个MySQL
- 丁磊推荐《你的灯亮着吗》为三大管理必读书
- 宏观经济的基本指标及其衡量
- 粒子群算法的几个适应度评价函数
- win7家庭版和旗舰版区别_Windows系统的家庭版、专业版、旗舰版,都有什么区别?...
- 动手学数据分析(四)- 数据可视化
- java开发常用软件下载地址及教程。
- macchanger克隆MAC地址
- 筋膜枪方案-无刷马达方波运用1
- 选择system bus还是session bus?
- 什么是飞书机器人?如何定时发送飞书机器人消息?
- IDEA更新至2020版后Version Control窗口的还原问题
- linux . 乱码,解决Linux乱码现象
- Opencv(C++)系列学习---opencv_contrib安装
热门文章
- echart移上去显示内容_echarts如何移动到柱状图上显示自己想显示的提示信息
- 介绍几个医学图像处理会议
- ASP.NET Core Web 应用程序系列(三)- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)...
- parentNode,parentElement,childNodes,children的区别
- 《深入理解Spark-核心思想与源码分析》(四)第四章存储体系
- python入门学习的第三天
- 类模板与运算符重载(一个简单的例子)
- HDU 3342 Legal or Not(拓扑排序)
- 组合数学--约瑟夫环问题 Josephus
- 微信分享无响应的解决