位运算之美——用+,-和位运算实现正整数除法和取模(二)

作者: 翼帆@cppblog
原文地址: http://www.cppblog.com/xiaoyisnail/archive/2009/09/21/96883.html
本文版权归作者和cppblog共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

终于有时间写本文的第二部分了。在 上一篇文章(下文中称为“文(一)”)中,我提出了一个具体的问题“ 只能用+,-和位运算实现 整数除法(/)和取模(%)”,并整理了一些和位运算相关的题目和算法,本文将给出上述问题的一个完整的解答思路和实现。

首先思考最简单的除法实现,即循环减除数,减到不能再减为止,所减次数即所求的商,事实上这就是我们初学四则运算时对除法的定义,实现:
 int integer_div_0(int dividend, int divisor)
 {
     if(divisor == 0)
     {
         cout<<"非法参数,除零错"<<endl;
         exit(1);
     }
      int res = 0;
    while((dividend-=divisor)>=0)
        ++res;
    return res;
}
很显然,这个简单的实现是非常低效的,求integer_div_0(a,b)的时间复杂度为O(a/b),当a很大b很小时,计算开销很大,有什么办法能提高效率吗?考虑到循环减除数同时比较差是否大于等于0有点类似于在一个一个数组或是linked list中顺序搜索某个目标值,在较差的情况下近似于搜索整个问题空间,这样的开销必然很大,那么只要找到某个方法来减少需要搜索的问题空间就可以提高性能了。于是,自然可以想到如下算法:以除数为初始测试值,以2的指数为步长来搜索问题空间,当被除数与测试值的差小于除数时便结束搜索,若在这之前测试值大于被除数,则将被除数减去前一个测试值,并重复上述过程直到搜索结束。举个例子,求1200/3:
顺序搜索时,我们要与1200比较的数有:3,6,9,12,15,...,1998,2001,比较次数667次

以2的指数为步长搜索时,与1200比较3,6,12,24,48,96,192,384,768,1536,然后与1200-768=432再进行比较3,6,12,24,48,96,192,384,768,再取432-384=48比较3,6,12,24,48,搜索结束,比较次数共24次,比顺序搜索有很大的提高。你可能会问,为什么要以2的指数为步长来搜索呢?答案是,这样我们就可以使用位操作来进一步提高计算效率了。下面是这个算法的实现:

 

 int integer_div_1(unsigned int dividend, unsigned int divisor)
 {
     if(divisor == 0)
     {
         cout<<"非法参数,除零错"<<endl;
         exit(1);
     }
 
     if(dividend < divisor) return 0;
    unsigned int k=0,c=divisor, res=0;
    
    for(;dividend>=c;c<<=1,k++)
        if(dividend-c < divisor)
            return 1<<k;
    return integer_div_1(dividend-(c>>1), divisor)+(1<<(k-1));
}
注意到最后一行的尾递归,再把代码优化为非递归如下:
//非递归整数除法
 int integer_div_2(unsigned int dividend, unsigned int divisor)
 {
     if(divisor == 0)
     {
         cout<<"非法参数,除零错"<<endl;
         exit(1);
     }
 
    if(dividend < divisor) return 0;
    unsigned int k, c, res=0;

    while(dividend > divisor)
    {
        for(k=0,c=divisor;dividend>=c;c<<=1,k++)
        {
            if(dividend-c < divisor)
            {
                res += 1<<k;
                break;
            }        
        }
        if(dividend-c < divisor)
            break;

        res += 1<<(k-1);
        dividend -= c>>1;
    }

    return res;
}

最后,有了整数除法,取模运算就很简单了,从进行整数除法搜索商的最后一步立刻就能得到模除的余数,实现如下,为了方便起见,代码里使用C++ STL中的pair模板以同时返回商和余数:
 //整数除法and取模,返回商和余数
 pair<int,int> integer_div_3(unsigned int dividend, unsigned int divisor)
 {
     if(divisor == 0)
     {
         cout<<"非法参数,除零错"<<endl;
         exit(1);
     }
 
    if(dividend < divisor) 
        return make_pair(0, dividend);
    unsigned int k, c, quotient=0, remainder;

    while(dividend > divisor)
    {
        for(k=0,c=divisor;dividend>=c;c<<=1,k++)
        {
            if(dividend-c < divisor)
            {
                quotient += 1<<k;
                remainder = dividend-c;
                break;
            }        
        }
        if(dividend-c < divisor)
            break;

        quotient += 1<<(k-1);
        dividend -= c>>1;
    }

    return make_pair(quotient, remainder);
}

写到这里,算是把全文的任务都完成了,读者可能觉得文(一)和文(二)里讲到的一些题目的关系不大,但我写这两篇文章的目的就是想强调 位运算的作用,或者说 二进制的美,很多时候如果我们换个角度,用二进制来思考问题,也许会突然“Aha!Insight!”,从而得到一个优美的解答。


附上代码:http://www.cppblog.com/Files/xiaoyisnail/bits.rar

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值