利用从左到右二进制幂算法计算二进制的幂(霍纳法则升级版,变治法)


题目描述

7.a.应用从左到右二进制幂算法来计算 a 17 a^{17} a17

本题目来源:《算法设计与分析基础》(美)莱维汀Leviti,A.)著;潘彦译.一3版.北京:清华大学出版社,2015 (2020. 12重印) 一书中第五章(分治法)课后习题6.5的第三题的a小问,对应原书P186(读者可自行对照)

注:本章节(第六章)主要讲述的是变治法,并对霍纳法则的特性以及复杂度进行了一定的分析,从左到右的二进制幂算法是基于霍纳法则算法而演变出来的,下面对霍纳法则进行讨论。


一、二进制幂的计算

当使用霍纳法则来计算 a n a^{n} an时,霍纳法则(又称秦九韶算法)的高效率的优点就被抹掉了,在对 a n a^{n} an的计算时,无论如何提出公因子来进行计算,都无法避免退化成对a进行蛮力自乘的算法,因此,我们引入了二进制幂计算的算法。

不太懂霍纳法则的同志可戳这里百度百科:霍纳法则

这里以从左至右计算 a 13 a^{13} a13为例(对应原书P185页例2),这里 n    =    13 =    110 1 2 n\,\,=\,\,13 =\,\,1101_2 n=13=11012
(将十进制转化为二进制),因此,我们有

n的二进制位1101
累乘器 a a a a 2 × a = a 3 a^{2}\times a = a^{3} a2×a=a3 ( a 3 ) 2 \left( a^3 \right) ^2 (a3)2 ( a 6 ) × a \left( a^6 \right) \times a (a6)×a

观察上述的算法,我们可以得到规律如下,用product表示累成器暂时的计算结果
p r o d u c t i    =    { p r o d u c t i    =    ( p r o d u c t i − 1 ) 2 × a    , n 的第 i 位二进制为 1 p r o d u c t i    =    ( p r o d u c t i − 1 ) 2    , n 的第 i 位二进制为 0 (1) product_i\,\,=\,\,\begin{cases} product_i\,\,=\,\,\left( product_{i-1} \right) ^2\times a\,\,, n\text{的第}i\text{位二进制为}1\\ product_i\,\,=\,\,\left( product_{i-1} \right) ^2\,\,, n\text{的第}i\text{位二进制为}0\\ \end{cases}\tag{1} producti={producti=(producti1)2×a,n的第i位二进制为1producti=(producti1)2,n的第i位二进制为0(1)

这样子0101的间隔乘法其实最后会导致幂次的奇数和偶数的差异, 具体推导过程见原书第六章

即对应代码中的部分如下

for (int j = I-1; j >= 0; j--)
	{//由于前面while存储循环的时候是从左->右对应低位->高位
	 //故这里的代码是从后往前计算
		if (*(p + j) == 1)product = product * product * a;
		else product = product * product;
	}

二、题解

1.源代码

注:本代码实现过程仅仅使用int来对结果进行存储,对于一些较大幂次的计算可能不适用,读者可以自行定义能储存更多结果的数据结构或者使用高精度乘法,或者大数乘法来进行计算。

/*
Author: FeverTwice
Date:   2021 - 04 - 22
Function:	计算a的17次方

PS:Homework of Algorithm lesson, Chapter 6
*/

#include <iostream>
#include <cstdio>
#include <string>
#include <cmath>
using namespace std;

int dec2bin(int b)
{//十进制转二进制
	int res = 0,j=1;

	while (b>0){
		res = res + j * (b % 2);
		b /= 2;
		j *= 10;
	}
	return res;
}

int leftBrinaryExp(int a, int b) {
	int I = floor(log(b) / log(2));//计算b转化为二进制之后的位数,向下取整
	int *p = new int[I+1];	//定义动态数据组
	int b2 = dec2bin(b);	//十进制转化为二进制

	int i = 0;
	while (b2>0)
	{//将二进制数逐位分离
		*(p + i) = b2 % 10;
		b2 /= 10;
		i++;
	}

	int product = a;	//二进制的最高位一定为1,初始化product

	for (int j = I-1; j >= 0; j--)
	{//由于前面while存储循环的时候是从左->右对应低位->高位
	 //故这里的代码是从后往前计算
		if (*(p + j) == 1)product = product * product * a;
		else product = product * product;
	}

	return product;
}

int main()
{
	cout << "请输入底数a与次数b" << endl;

	int a, b;//a为底数,b为次数
	cin >> a >> b;
	
	if (b == 0)cout << 1 << endl;
	else {
		int res = leftBrinaryExp(a, b);
		cout << res << endl;	//输出结果
	}

	return 0;
}

2.源程序测试

注:本题源程序测试环境Visual Studio 2019

1.Test set 1
10000^0

2.Test set 2

1^10000

3.Test set 3
2^2

4.Test set 4
2^11


写在最后

各位看官,都看到这里了,麻烦动动手指头给博主来个点赞8,您的支持作者最大的创作动力哟! <(^-^)>
才疏学浅,若有纰漏,恳请斧正
本文章仅用于各位同志作为学习交流之用,不作任何商业用途,若涉及版权问题请速与作者联系,望悉知

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值