《编程原本 》一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 请自己确认最后三行代码是正确的. 
weixin151云匹面粉直供微信小程序+springboot后端毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值