《编程原本 》一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 请自己确认最后三行代码是正确的. 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/257848.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Effective C++ .07 virtual析构函数的提供

主要讲了&#xff0c; 1. virtual析构函数的作用与调用顺序 2. 使用时机&#xff0c;并不是使用了继承就要把基类的析构函数变为虚函数&#xff08;virtual&#xff09;&#xff0c;只有当用于多态目的时才进行一个virtual析构函数的定义。 3. 不要继承那些没有将析构函数定义为…

OnClickListener冲突的问题

OnClickListener冲突的问题 (2011-11-26 15:28:27) 转载▼标签&#xff1a; 杂谈 分类&#xff1a; android学习记录 import anfroid.view.View.OnClickListenerimport anfroid.content.DialogInterface.OnClickListener 这两个东西要同时用的话&#xff0c;要使用以下方式&…

html 响应式 同一行,一行CSS实现各种响应式元素 – Fluidity

一行CSS实现各种响应式元素 – Fluidity3月 31, 2014评论SponsorFLUIDITY是一个极微小的CSS样式表&#xff0c;压缩版只有一行代码&#xff0c;大小只有115个字节&#xff0c;它能实现图像、文本、Canvas、Table表格以及iFrame框架的响应式功能。好用且实用&#xff01;这个响应…

玩C一定用得到的19款Java开源Web爬虫

网络爬虫(又被称为网页蜘蛛&#xff0c;网络机器人&#xff0c;在FOAF社区中间&#xff0c;更经常的称为网页追逐者)&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。 今天将为…

一元二次方程

转载于:https://www.cnblogs.com/569114a/p/4179164.html

cookie html5,HTML5——存储(cookie、localStorage、sessionStorage)的区别

cookie本来用于客户端和服务端通信&#xff0c;但是因为它有本地存储的功能&#xff0c;于是被“借用”了。使用方法document.cookie 获取和修改即可缺点存储量太少&#xff0c;只有4kb所有http请求都带着&#xff0c;会影响获取资源的效率。API简单&#xff0c;需要封装才能使…

数据中心存在不当投资吗?

不正当的投资是一种危害&#xff1a;在一些项目建设中&#xff0c;投入大量的资金是错误的&#xff0c;因为这些项目的需求是不可持续的或高估的。那么数据中心属于这一类吗? 投资不当的问题 不当投资会与经济的繁荣与萧条齐头并进。例如&#xff0c;抑制按揭贷款利率可能会导…

问题:循环元素,被选中元素个数,全选

一段时间不写js都有点忘记了&#xff0c;这里看几个常见的js&#xff0c;涉及到循环&#xff0c;计算元素个数&#xff0c;checkbox选中等问题&#xff0c;首先是html元素 <div class"content border p05"><div><input type"checkbox" id&q…

LeetCodeOJ. String to Integer (atoi)

试题请參见: https://oj.leetcode.com/problems/string-to-integer-atoi/ 题目概述 Implement atoi to convert a string to an integer.Hint: Carefully consider all possible input cases. If you want a challenge, please do not see below and ask yourself what are the…

html如何设置滚动动画,JavaScript 实现页面滚动动画

在做前端 UI 效果时&#xff0c;让元素根据滚动位置实现动画效果是一个非常流行的设计&#xff0c;通常我们会使用第三方插件或库来实现。在本教程中&#xff0c;我将教大家使用纯 JavaScript 和 CSS 来实现。先预览一下实现的效果&#xff1a;我们使用 CSS 来实现动画&#xf…

oraclenbsp;一个稍微大点数据库

公司有个水电收费系统,在包头试运行, 给了我一个dmp让熟悉一下业务. dmp是压缩过的.80多兆好像.解压下300多兆好像.导入, 有几个表是50万行的,几个30万左右,200多个表(没数).很多表是0行,设计居民用户单位用户一堆一堆的.用户有几万个. 问题是这样的: 收费系统每个月要结算一个…

绝非玩笑!人工智能或开创黑客新时代

专家称&#xff0c;未来的网络战争可能是机器对机器&#xff0c;这可能需要几年甚至几十年时间&#xff0c;但黑客并不一定总是人类。人工智能(AI)是可彻底改变网络安全的技术&#xff0c;而它有一天可能成为最终的攻击工具。 今年8月由美国国防部先进项目研究局(DARPA)赞助的C…

redis 笔记06 发布与订阅、事务、慢查询日志、监视器

发布与订阅 1. 服务器状态在pubsub_channels字典保存了所有频道的订阅关系&#xff1a;SUBSCRIBE命令负责将客户端和被订阅的频道关联到这个字典里面&#xff0c;而UNSUBSCRIBE命令则负责 解除客户端和被退订频道之间的关联。 2. 服务器状态在pubsub_patterns链表保存了所有模式…

html中文段落,HTML 段落-JavaScript中文网-JavaScript教程资源分享门户

HTML 可以将文档分割为若干段落。HTML 段落段落是通过 标签定义的。实例这是一个段落 这是另一个段落尝试一下 注意&#xff1a;浏览器会自动地在段落的前后添加空行。( 是块级元素)不要忘记结束标签即使忘了使用结束标签&#xff0c;大多数浏览器也会正确地将 HTML 显示出来&a…

自学python系列14:映像,集合类型-集合类型

集合类型1.1如何创建集合类型和给集合赋值1.1.1 如何创建集合类型和给集合赋值集合的工厂方法set()和frozenset()>>> sset(abc)>>> sset([a, c, b])>>> tfrozenset(abc)>>> tfrozenset([a, c, b])len()计算的是集合的字母的个数1.1.2如何访…

观点:我们为什么需要威胁情报?

最近被谈论的异常火热的一个术语就是威胁情报&#xff0c;那么威胁情报到底是什么意思&#xff0c;它是一种什么概念或者机制呢?本文中我们就来亲密接触一下威胁情报&#xff0c;并了解它所具有的功能&#xff0c;然后给出几个威胁情报的最佳实践示例&#xff0c;最后分析威胁…

vijos 1942 [AH 2005] 小岛

描述 西伯利亚北部的寒地&#xff0c;坐落着由 N 个小岛组成的岛屿群&#xff0c;我们把这些小岛依次编号为 1 到 N 。 起初&#xff0c;岛屿之间没有任何的航线。后来随着交通的发展&#xff0c;逐渐出现了一些连通两座小岛的航线。例如增加一条在 u 号小岛与 v 号小岛之间的航…

聊城大学计算机应用基础函授,聊城大学试题计算机应用基础试题

姓名 年级专业层次 教学单位密封线 第1页 共3页聊城大学《计算机应用基础》函授试题一、判断题(共10题&#xff0c;每题2分&#xff0c;共20分)1、信息按状态划分可以划分为动态信息和静态信息。( √ )2、操作系统不具有通用性。( )3、在Windows XP环境中&#xff0c;整个显示…

Struts2中 Path (getContextPath与basePath)

struts2中的路径问题是根据action的路径而不是jsp路径来确定&#xff0c;所以尽量不要使用相对路径。 虽然可以用redirect方式解决&#xff0c;但redirect方式并非必要。解决办法非常简单&#xff0c;统一使用绝对路径。&#xff08;在jsp中用request.getContextpath方式来拿到…

(七)SpringBoot+SpringCloud —— 集成断路器

2019独角兽企业重金招聘Python工程师标准>>> 断路器简介 在一个项目中&#xff0c;系统可能被拆分成多个服务&#xff0c;例如用户、订单和库存等。 这里存在这服务调用服务的情况&#xff0c;例如&#xff0c;客户端调用订单服务&#xff0c;订单服务又调用库存服务…