书接上回:埃及乘法算法详解_who_am_i__的博客-CSDN博客
我们今天来看看如何优化改进埃及乘法算法的代码,从上回的代码中我们可以看到我们已经把原来需要计算n次的加法运算缩减到了log n次加法运算(埃及乘法算法)。但是上文中用到的是函数的递归调用,我们知道在多次递归调用函数时会有很大一部分开销。为了减去这部分开销我们要把递归调用改成迭代的形式。
首先我们用c++实现一个这样的函数:
f(r,n,a) = r + na
这里同样使用埃及乘法算法计算na,首先我们做下分析:
当n=1时:f(r,1,a) = r+a;
当n为奇数时:f(r,n,a) = f(r+a,n/2,a+a) = r+a + (n-1)/2 *2a = r + na; // 在编程中n为奇数时(n-1)/2=n/2
当n为偶数时:f(r,n,a) = f(r,n/2,a+a) = r+n/2*2a=r+na;
那么代码就显而易见了:
// 利用埃及乘法算法计算r+na;这里的odd(n)和half(n),请看上一篇博文
int mult_acc0(int r,int n, int a){
if(n == 1) return r+a;
if(odd(n)){
return mult_acc0(r+a,half(n),a+a);
}else{
return mult_acc0(r,half(n),a+a);
}
}
从上面的代码可以看出来当n为奇数和偶数时只有一处不同:即第一个参数:r+a和r,这样我们就可以去掉一个递归调用:
int mult_acc1(int r,int n, int a){
if(n == 1) return r+a;
if(odd(n)) r += a;
return mult_acc1(r,half(n),a+a);
}
于是这个函数就变成尾递归了,也就是说他只会在返回值里进行递归。现在我们来看该函数的另外两项特征:
n的值很少会取到1
如果n为偶数,那就没必要在判断他是不是等于1了
基于这两个特征我们可以先判断n是否为奇数,然后再判断他是不是1,这样就可以把和1的比较次数缩减为原来的一半:
int mult_acc2(int r,int n, int a){
if(odd(n)){
r += a;
if(n==1) return r;
}
return mult_acc2(r,half(n),a+a);
}
看到这里你可能会觉得,上面讲了那么多不还是递归调用吗,而且我们要的时n*a的优化方法,你讲r+n*a有什么用呢。这个会在后续一一解答。
下面进行最终要的一步:将上述函数修改声严格的尾递归函数。严格尾递归函数:指的是那种使用与形式参数完全对应的实际参数,来进行所有递归调用的尾递归过程。
将mult_acc2转化为严格尾递归函数:
int mult_acc3(int r,int n, int a){
if(odd(n)){
r += a;
if(n==1) return r;
}
n = half(n);
a = a + a;
return mult_acc3(r,n,a); // 这里的形参与实参完全对应
}
至此,我们就可以用while(true)结构来代替尾递归了:
int mult_acc4(int r,int n, int a){
while(true){
if(odd(n)){
r += a;
if(n==1) return r;
}
n = half(n);
a = a + a;
}
}
以上函数我们可以称之为乘法累加函数,因为r对a+a进行了累加。那么我们就可以使用上述函数计算na:
int multiply(int n,int a){
if(n == 1) return a;
return mult_acc4(a,n-1,a);
}
至此已完成递归到迭代的优化。但是这种方式还存在些许问题,比如说当n时2的整数次幂时,对于mult_acc4,执行while(odd(n)){n=(n-1)/2;} n永远是个奇数,这就导致mult_acc4此时执行效率最低。