3.2 计算乘幂
对可结合运算op计算an的算法以a,n和op为参数,这里a的类型是op的定义域,n必须属于某个整数类型.如果没有可结合性假设,可以用两个算法分别完成从左到右或从右到左的计算:
template<typename I, typename Op>
requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power left associated(Domain(Op) a, I n, Op op) {
// 前条件: n> 0
if (n == I(1)) return a;
return op(power left associated(a, n -I(1), op), a); }
template<typename I, typename Op>
requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power right associated(Domain(Op) a, I n, Op op) {
// 前条件: n> 0
if (n == I(1)) return a;
return op(a, power right associated(a, n -I(1), op)); }
这两个算法都应用相应的运算n.1次.对于非可结合的运算,它们可能返回不同结果.作为例子,请考虑减法运算的1到3次幂的结果.
当a和n都是整数而运算是乘法时,这两个算法给出的都是乘幂;运算是加法时两个算法做的都是乘法.古埃及人早已发现了更快的乘法算法,可以将其推广到计算任意可结合运算的幂.1
可结合性使人可以自由地将运算分组,由此可得
template<typename I, typename Op>
requires(Integer(I) && BinaryOperation(Op))
Domain(Op) power 0(Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
if (n == I(1)) return a;
if (n % I(2) == I(0))
return power 0(op(a, a), n / I(2), op);
return op(power 0(op(a, a), n / I(2), op), a);
}
现在对指数n算一下power0执行运算的次数.递归调用的次数是.log2 n..令v为n的二进制表示里1的个数.每次递归调用执行一次运算求a的平方,还需要v.1次额外的运算调用,所以总运算次数是
.log2 n. +(v . 1) . 2.log2 n.
对于n = 15, .log2 n. =3,其表示中有4个1,公式给出的是6次运算.另一不同分组方式是a15 =(a 3)5 , 这里a 3 做2 次运算而a 5 做3次,总数为5.对另外一些指数也存在更快的分组方式,例如对23,27,39和43.2
powerleftassociated需要做n.1次运算,而power0最多做2.log2 n. 次运算.很明显,对于较大的n,power0总是快得多.但事情也不都这样.例如,要是做具有任意精度整数系数的一般多项式的乘法,powerleftassociated会更快些.3 还有,即使对这样的简单算法,我们也不知道如何精确描述一种复杂性需求,使之可用于确定两者中哪个更好.
power0能处理很大的指数,如10300 , 这使它在密码学中非常重要.