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

来源:互联网 发布:2016好声音网络直播 编辑:程序博客网 时间:2024/06/04 20:02

 

9月21日,对本文从格式到部分内容上都进行了修改另外,鉴于某些转载没有注明出处,考虑到版权问题,特声明如下:作者:翼帆@cppblog  原文地址:http://www.cppblog.com/xiaoyisnail/archive/2009/09/19/96707.html本文版权归作者和cppblog共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。     今天看了一位师兄去年的笔经总结,其中有一题是“不许用%和/来实现求任意数除以3的余数”,我想考官的目的应该是想考察学生对位运算的熟悉程度吧,于是我把题目扩展成“只能用+,-和位运算实现正整数除法(/)和取模(%)”,注意:这里不能使用其它的库例程来辅助计算,如log,log10等。在思考这道题目的过程中,我又涉及到了许多二进制相关的题目,如:    判断给定的整数是不是2的整数次幂    判断给定的整数是不是4的整数次幂    求给定整数的二进制表示中1的个数    求给定整数的二进制表示中0的个数    求给定整数的二进制表示中最高位1的位置    求大于等于给定整数的最小的2的整数次幂    求给定整数的二进制表示的有效位数    ...    9月21日补充:这里只考虑值为正整数的情况。    这些题目都是经典老题,频繁出现于各类笔试面试题中,除了能考察位运算外,还能考察应聘者能否给出创新的算法来更好地解决问题。可以说这些题目都不难,如果使用32位的int来表示整数的话,蛮力法都可以比较好地完成任务,但是如果想尽可能地提高效率,那就需要动一番脑经了。下面给出我对这些问题的整理和C++实现,并在下次的文章中给出只用+,-和位运算实现的正整数除法和取模。    从某种意义上讲,特别是从充分利用底层硬件的计算能力(利用特殊的cpu指令)来看,这些解法肯定不是最优的,所以还希望大侠们多多指点。        判断给定的整数是不是2的整数次幂    这应该是最简单的,利用最高位是1,其后所有位为0的特性,常数时间解决问题:1 //判断n是否是2的正整数冪2 inline bool is_2exp(unsigned int n)3 {4     return !(n&(n-1));5 }    求给定整数的二进制表示中1的个数    考虑到n-1会把n的二进制表示中最低位的1置0并把其后的所有0置1,同时不改变此位置前的所有位,那么n&(n-1)即可消除这个最低位的1。这样便有了比顺序枚举所有位更快的算法:循环消除最低位的1,循环次数即所求1的个数。此算法的时间复杂度为O(n的二进制表示中的1的个数),最坏情况下的复杂度O(n的二进制表示的总位数)。 1//计算n的二进制表示中1的个数 2inline int count1(unsigned int n) 3{ 4    int r = 0; 5    while(n) 6    { 7        n &= n-1; 8        r++; 9    }10    return r;11}    既然有了求给定整数的二进制表示中1的个数的办法,那么想要求给定整数的二进制表示中0的个数就很简单了。事实上,在二进制中,完全可以把0和1看作是对称的两个对象,取反操作(~)可以任意的切换这两个对象,只要先对n进行一次取反,然后再用上述算法即能得到二进制表示中0的个数。首先看下面的代码: 1//计算n的二进制表示中0的个数 2inline int count0_wrong(unsigned int n) 3{ 4    int r = 0; 5    n &= ~n; 6    while(n) 7    { 8        n &= n-1; 9        r++;10    }11    return r;12}    不知大家有没有看出问题来?是的,~操作符会把所有高位的都取反,而不是只把有效位取反,所以我们需要一个能保持高位不变的位取反操作,下面是我的实现,时间复杂度和求二进制表示中1的个数的算法相同,都与二进制表示中1的个数有关: 1//保持高位取反 2inline unsigned int negate_bits(unsigned int n) 3{ 4    if(n==0) return 1; 5    unsigned int r=0, m=~n; 6    while(n) 7    { 8        r |= (n^(n-1))&m; 9        n &= n-1;10    }1112    return r;13}    有了这个特殊的取反操作,求给定整数的二进制表示中0的个数的办法就简单了: 1//计算n的二进制表示中0的个数 2inline int count0( unsigned int n) 3{ 4    int r = 0; 5    n = negate_bits(n); 6    while(n) 7    { 8        n &= n-1; 9        r++;10    }11    return r;12}    看到这里,聪明的读者肯定看出问题来了,其实我干了一件很蠢的事情。看看上述算法的时间复杂度,negate_bits花了O(n的二进制表示中1的个数),while循环计算取反后的n的二进制表示中1的个数,事实上就是O(n的二进制表示中0的个数),两部分加起来其实就是二进制表示总的有效位数,换句话说,这个算法是线性的,而事实上,我们完全可以先线性地求出这个总的有效位数,然后减去1的位数,即得到0的位数,根本不用费那么大劲去整个保持高位的取反操作,两者的时间复杂度在渐进意义上也是相同的。所以,我犯傻了,但是这里又引出另一个问题:    求给定整数的二进制表示的有效位数    上面提到了线性地求这个位数(下文记为m),即“循环右移1位,记录右移次数”,时间复杂度O(m)。但是我想,一看到这个题目,所有人的第一反应应该是floor(log2(n))+1吧,但是请注意,本文在一开始就规定了“不能使用库例程”,那么在这个限制下该怎么做呢?有没有比线性时间更好的算法呢?其实到目前为止我也没有什么特别好的算法,希望谁有什么精妙的算法能指点一下,不要打我。。。 1//求给定整数的二进制表示的位数,线性算法 2int count_bit(unsigned int n) 3{ 4    int r = 0; 5    while(n) 6    { 7        n>>=1; 8        r++; 9    }10    return r;11}    求大于等于给定整数的最小的2的整数次幂    首先是最简单的思路:求出n的二进制表示的总位数m,于是1<<m即为所求值,当然这里要排除n自身就是2的整数次幂的情况,复杂度O(m),实现如下: 1//求大于等于n的最小的2的正整数冪,方法1 2//时间复杂度O(n的二进制位长度) 3unsigned int high_2exp_1(unsigned int n) 4{ 5    if(n<=1) return 1; 6    if(is_2exp(n)) return n; 7 8    unsigned int r = 1; 9    while(n)10    {11        n >>= 1;12        r <<= 1;13    }1415    return r;16}    事实上这就涉及到上面求二进制表示位数的问题,所以目前为止在此基础上的算法都是线性时间的。        那有没有不用计算位数m,从而效率更好的算法呢,能不能像在计算二进制表示中1的个数时那样根据1的个数来设计算法呢?回到那一题中,“n-1会把n的二进制表示中最低位的1置0并把其后的所有0置1”,那么n|=n-1就把n的二进制表示中最低位1后的所有0置1,再加上1,那么就把最低位1左移了一位。于是,便有了更好的算法:循环左移最低位的1,直到n是2的整数次幂。该算法跟二进制表示中的1的个数和位置有关,最坏时间复杂度还是O(二进制表示位数),但是比起上一个实现,这个算法在多数情况下都比上一个算法快。实现如下: 1//求大于等于n的最小的2的正整数冪,方法2 2//计算时间与n的二进制表示中1的个数和位置有关,比方法1效率高 3//最坏情况下的时间复杂度与方法1相同 4unsigned int high_2exp_2(unsigned int n) 5{ 6    if(n<=1) return 1; 7 8    while(!is_2exp(n)) 9    {10        n |= n-1;11        n++;12    }1314    return n;15}        最后来一个简单的扩充题目:    判断给定的整数是不是4的整数次幂    观察4的整数次幂的特征,容易发现除了满足n&(n-1)==0外,唯一的1位后的0的个数是偶数,这从4x=22k也能简单地得到。这就很直观地衍生出一个简单的算法: 1//判断n是否是4的整数次幂 2bool is_4exp(unsigned int n) 3{ 4    if(!is_2exp(n)) return false; 5 6    int bit_len = count_bit(n)-1;//线性时间求二进制位数 7    if((bit_len&0x1)!=1) 8        return true; 9    else10        return false;11}    算法很直观,但是比起is_2exp的常数时间is_4exp的线性时间总让我觉得不能接受,不过无奈还是没有想出好办法来,哎。。。求大牛指点啊        说明:写这篇文章,已经三次丢失全文了,把我快搞疯了,firefox下好像有点问题,先把文章发上来,过会儿换到IE下继续。。。    再说明:换了IE后就没再出问题了,不过写着写着发现写了好久,先歇会儿,得看书补习功课了     最后的说明:下次会给出本文最初提出的问题(只能用+,-和位运算实现正整数除法(/)和取模(%))的实现。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 钱站一直闪退怎么办 win7重装连不上网怎么办 笔记本屏幕横过来了怎么办 3D贴图丢了怎么办 百度文库安装后手机打不开怎么办 win7系统不带usb驱动怎么办 手机网页上的pdf打不开怎么办 网页下载pdf后缀是.do怎么办 ps界面太小怎么办win10 ps软件打不开程序错误怎么办 ps打开后 未响应怎么办 ps图层无法解锁怎么办 ie8浏览器电脑不能用怎么办 系统要ie6.0才能打开怎么办 2g手机内存不够怎么办 2g运行内存不够怎么办 手机运行内存2g不够怎么办 手机无法加载程序秒退怎么办 电脑账户密码忘记了怎么办 玩绝地求生卡顿怎么办 地下城总运行时间错误怎么办 逆战更新太慢怎么办 win7我的电脑没了怎么办 剑灵启动游戏慢怎么办 网页页面结束进程也关不掉怎么办 开机就启动微信怎么办 微信突然无法启动怎么办 微信发送太频繁怎么办 微信在电脑上打不开文件怎么办 微信照片电脑上打不开怎么办 换一部手机微信怎么办 微信支付宝停止运行怎么办 剑三重制版卡顿怎么办 剑三客户端更新不动了怎么办 安装包安装失败怎么办有内存 qq飞车换手机了怎么办 qq飞车求婚失败戒指怎么办 改脸型皮肤会下垂怎么办 情侣关系弄僵了怎么办 用微信交话费没有到账怎么办 微信交错话费了怎么办