[关闭]
@chawuciren 2018-11-22T12:04:08.000000Z 字数 1265 阅读 931

如何只用加号和位移实现两个数的乘法

CSI


递归

第一种情况:第二个因数为偶数(这里只讨论第二个因数的奇偶)

假设我们要计算3*4,相当于计算3+3+3+3。可以看到里面有重复运算,比如先计算3+3=6,再计算6+6=12。这样就省掉了一次运算。
现在推广到计算3×n,第一次先计算a=3<<1,result=a+a然后n=n>>1,第二次计算result=result+a,然后n=n>>1......直到n==1。
接着我发现如果n>>1以后还是偶数,就能继续提高效率。
然后我就想,能不能设计n是偶数就进入第一种情况,n是奇数就进入第二种情况(包括右移后的n)。
n不停除二,除到最后不是二就是三。
假如是二就计算3+3,是三就在计算完毕之后再加3。
然后用递归实现。

  1. int mu(int a,int b){
  2. return b==1?a:mu(a<<1,b>>1);//如果b是奇数。。。还没想到最后一个怎么加
  3. }

答案

  1. int mu(int a,int b){
  2. if(b==0)
  3. return 0;
  4. if(b==1)
  5. return a;
  6. if(b&1)
  7. return b==1?a:2*mu(a,b>>1)+a;
  8. else
  9. return b==1?a:2*mu(a,b>>1);
  10. }

迭代

试图写迭代还是用了乘法
一开始想像递归那样乘,后来发现迭代相当于正序计算,递归相当于反序,所以当然是不行的。
考虑到如果是奇数,除二以后会多出来一个a,那额外计算这个a的数量就好了,其他就和偶数一样了。
然后,这个a的数量还是乘上去的。。。

  1. int mu(int a,int b){
  2. int t=a;//记录一下a
  3. int i=1;//记录一下右移了几次了(就是分了几个部分)
  4. int j=1;//分一次就将这个乘2的平方
  5. int n=0;//奇数的时候没计入的都放到这里
  6. if(b==0)
  7. return 0;
  8. if(b==1)
  9. return a;
  10. do{//
  11. a+=a;
  12. if(b&1){//
  13. n=n+t*j;//这里还是用了乘法。。。
  14. }
  15. j<<=i;//相当于乘2的平方
  16. b>>=1;//相当于除二
  17. }while(b!=1);
  18. a=a+n;//加起来就是结果
  19. return a;
  20. }

11.22

更新答案
所以正确的做法是什么?
把b看成一个二进制数字,用符号记数法将他转化为十进制。我们就会得到
b的每一位不是0就是1,剩下的只要算a*2就可以了
先是b取每一位

  1. int p=b&1;//存放这一位
  2. b=b>>1;//b右移一位,下一次迭代就可以取下一位

这一位是0啥也不用干(really?),这一位是1就要计算a×2
是0就不用加,不是0就要加,但是都要对a左移。
结果

  1. int mu(int a,int b){
  2. int res=0;
  3. do{
  4. int p=b&1;//存放这一位
  5. b=b>>1;//b右移一位,下一次迭代就可以取下一位
  6. if(p==1)
  7. res+=a;
  8. a<<=1;
  9. }while(b!=0);
  10. return res;
  11. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注