位运算之美

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的个数。

首先看下面的代码:

 //计算n的二进制表示中0的个数

 inline int count0_wrong(unsigned int n)

{     int r = 0; 

    n &= ~n;

  while(n)  

   { 

       n &= n-1;         r++;

    }

   return r;

}   

不知大家有没有看出问题来?是的,~操作符会把所有高位的都取反,而不是只把有效位取反,所以我们需要一个能保持高位不变的位取反操作,下面是我的实现,时间复杂度和求二进制表示中1的个数的算法相同,都与二进制表示中1的个数有关:

//保持高位取反

inline unsigned int negate_bits(unsigned int n)

   if(n==0) return

    unsigned int r=0, m=~n; 

    while(n) 

  { 

       r |= (n^(n-1))&m; 

        n &= n-1;10    }

    return r;

}    有了这个特殊的取反操作,求给定整数的二进制表示中0的个数的办法就简单了:

 //计算n的二进制表示中0的个数

 inline int count0( unsigned int n)

{    int r = 0; 

   n = negate_bits(n); 

 while(n)     { 

        n &= n-1; 

       r++;

    }

    return r;

}    看到这里,聪明的读者肯定看出问题来了,其实我干了一件很蠢的事情。看看上述算法的时间复杂度,negate_bits花了O(n的二进制表示中1的个数),while循环计算取反后的n的二进制表示中1的个数,事实上就是O(n的二进制表示中0的个数),两部分加起来其实就是二进制表示总的有效位数,换句话说,这个算法是线性的,而事实上,我们完全可以先线性地求出这个总的有效位数,然后减去1的位数,即得到0的位数,根本不用费那么大劲去整个保持高位的取反操作,两者的时间复杂度在渐进意义上也是相同的。所以,我犯傻了,但是这里又引出另一个问题:    求给定整数的二进制表示的有效位数    上面提到了线性地求这个位数(下文记为m),即“循环右移1位,记录右移次数”,时间复杂度O(m)。但是我想,一看到这个题目,所有人的第一反应应该是floor(log2(n))+1吧,但是请注意,本文在一开始就规定了“不能使用库例程”,那么在这个限制下该怎么做呢?有没有比线性时间更好的算法呢?其实到目前为止我也没有什么特别好的算法,希望谁有什么精妙的算法能指点一下,不要打我。。。

 //求给定整数的二进制表示的位数,线性算法

int count_bit(unsigned int n)

   int r =0; 5    while(n) 

   { 

        n>>=1; 

       r++;   

}   return r;

}    求大于等于给定整数的最小的2的整数次幂    首先是最简单的思路:求出n的二进制表示的总位数m,于是1<<m即为所求值,当然这里要排除n自身就是2的整数次幂的情况,复杂度O(m),实现如下:

 

1//求大于等于n的最小的2的正整数冪,方法1 2//时间复杂度O(n的二进制位长度)

unsigned int high_2exp_1(unsigned int n)

{    if(n<=1) return 1; 

   if(is_2exp(n)) return n; 

    unsigned int r = 1; 

   while(n)

    {

       n >>= 1;

       r <<= 1;

    }

    return r;

}    事实上这就涉及到上面求二进制表示位数的问题,所以目前为止在此基础上的算法都是线性时间的。        那有没有不用计算位数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相同

unsigned int high_2exp_2(unsigned int n) {     if(n<=1) return 1;  

    while(!is_2exp(n))    {

       n |= n-1;     n++;

   }

   return n;

}        最后来一个简单的扩充题目:    判断给定的整数是不是4的整数次幂    观察4的整数次幂的特征,容易发现除了满足n&(n-1)==0外,唯一的1位后的0的个数是偶数,这从4x=22k也能简单地得到。这就很直观地衍生出一个简单的算法:

 1//判断n是否是4的整数次幂

 bool is_4exp(unsigned int n) { 

    if(!is_2exp(n)) return false;  

   int bit_len = count_bit(n)-1;

//线性时间求二进制位数 

   if((bit_len&0x1)!=1) 

        return true; 

    else        return false;

}   

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值