zoukankan      html  css  js  c++  java
  • 常用位运算操作

    一. 位操作基础

    位运算符分为逻辑运算符(~、|、&、^)和移位运算符(<<、>>、>>>)。位运算操作的是二进制的数。

    逻辑运算符
    1、^(亦或运算) ,针对二进制,相同的为0,不同的为1。

    2、&(与运算) 针对二进制,只要有一个为0,就为0

    3、| 两个位只要有一个为1,那么结果就是1,否则就为0

    4、~ 取反

    5、原码 原码就是符号位加上数字的二进制表示。

    6、反码

      一个数如果为正,则它的反码与原码相同;一个数如果为负,则符号位为1,(符号位不变化,其余位数取反)。

    换言之 该数的绝对值取反(绝对值取反各位都取反)。

    为了简单起见,我们用1个字节来表示一个整数:

      +7的反码为:00000111

      -7的反码为: 11111000

    7、 补码

      补码:一个数如果为正,则它的原码、反码、补码相同;一个数如果为负,取到反码然后加1。(反码加1就是补码)为了简单起见,我们用1个字节来表示一个整数:

      +7的补码为: 00000111

      -7的补码为: 11111001

    移位运算符
      1、<<:左移,将运算符左边的运算对象向左移动右侧指定的位数。注意,是二进制表示的时候,相当于运算符左侧的运算对象乘以2(在不溢出的情况下),二进制形式最低位补0。

      2、>>:右移运算符,将运算符右边的运算对象向右移动指定的位数。相当于将运算符左边的数除以2,二进制形式对应的最高位补上符号位。(负数的二进制表示形式为其补码)

      3、>>>:无符号右移运算符,和右移运算一样,但是二进制表示时最高位补上时无论被操作数是正是负,补上的都是0。

    位运算优先级

      ~的优先级最高,其次是<<、>>和>>>,再次是&,然后是^,优先级最低的是|。

    注意
      若对char,byte或者short进行移位处理,那么在移位进行之前,先将它们转化成int。 只有右侧的5个低位才会用到。这样可防止我们在一个int数里移动不切实际的位数。 若对一个long值进行处理,最后得到的结果也是long。此时只会用到右侧的6个低位,防止移动超过long值里现成的位数。 但在进行“无符号”右移位时,也可能遇到一个问题。若对byte或short值进行右移位运算,得到的可能不是正确的结果,它们会自动转换成int类型,并进行右移位。但“零扩展”不会发生,所以在那些情况下会得到-1的结果。

    8、复合赋值运算符
    位运算符与赋值运算符结合,组成新的复合赋值运算符,它们是:

    1、&=   例:a &=b       相当于a=a& b
    2、|=   例:a |=b           相当于a=a |b
    3、>>=  例:a >>=b    相当于a=a>> b
    4、<<= 例:a<<=b      相当于a=a<< b
    5、^=   例:a ^= b       相当  a=a ^b

    运算规则:和前面讲的复合赋值运算符的运算规则相似。

    9、不同长度的数据进行位运算
    如果两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算。

    以“与”运算为例说明如下:如果一个4个字节的数据与一个2个字节数据进行“与”运算,右端对齐后,左边不足的位依下面三种情况补足:

    (1)如果整型数据为正数,左边补16个0。

    (2)如果整型数据为负数,左边补16个1。

    (3)如果整形数据为无符号数,左边也补16个0。

    二. 常用位操作小技巧

    2.1 判断奇偶

    只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if (a & 1 == 0)代替if (a % 2 == 0)来判断a是不是偶数。

    下面程序将输出0到100之间的所有奇数。

    1 for (i = 0; i < 100; ++i)  
    2     if (i & 1)  
    3         printf("%d ", i);  
    4 putchar('
    ');  

    2.2 交换两数

    一般的写法是:

    1 void Swap(int &a, int &b)  
    2 {  
    3     if (a != b)  
    4     {  
    5         int c = a;  
    6         a = b;  
    7         b = c;  
    8     }  
    9 }  

    可以用位操作来实现交换两数而不用第三方变量:

    1 void Swap(int &a, int &b)  
    2 {  
    3     if (a != b)  
    4     {  
    5         a ^= b;  
    6         b ^= a;  
    7         a ^= b;  
    8     }  
    9 }  

    可以这样理解:

    第一步  a^=b 即a=(a^b);

    第二步  b^=a 即b=b^(a^b),由于^运算满足交换律,b^(a^b)=b^b^a。由于一个数和自己异或的结果为0并且任何数与0异或都会不变的,所以此时b被赋上了a的值。

    第三步 a^=b 就是a=a^b,由于前面二步可知a=(a^b),b=a,所以a=a^b即a=(a^b)^a。故a会被赋上b的值。
    再来个实例说明下以加深印象。int a = 13, b = 6;

    a的二进制为 13=8+4+1=1101(二进制)

    b的二进制为 6=4+2=110(二进制)

    第一步 a^=b  a = 1101 ^ 110 = 1011;

    第二步 b^=a  b = 110 ^ 1011 = 1101;即b=13

    第三步 a^=b  a = 1011 ^ 1101 = 110;即a=5

    2.3 变换符号

    变换符号就是正数变成负数,负数变成正数。

    如对于-11和11,可以通过下面的变换方法将-11变成11

          1111 0101(二进制) –取反-> 0000 1010(二进制) –加1-> 0000 1011(二进制)

    同样可以这样的将11变成-11

          0000 1011(二进制) –取反-> 0000 1010(二进制) –加1-> 1111 0101(二进制)

    因此变换符号只需要取反后加1即可。完整代码如下:

     1 //by MoreWindows( http://blog.csdn.net/MoreWindows )    
     2 #include <stdio.h>  
     3 int SignReversal(int a)  
     4 {  
     5     return ~a + 1;  
     6 }  
     7 int main()  
     8 {  
     9     printf("对整数变换符号 --- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---
    
    ");  
    10     int a = 7, b = -12345;  
    11     printf("%d  %d
    ", SignReversal(a), SignReversal(b));  
    12     return 0;  
    13 }  

    2.4 求绝对值

    位操作也可以用来求绝对值,对于负数可以通过对其取反后加1来得到正数。对-6可以这样:

          1111 1010(二进制) –取反->0000 0101(二进制) -加1-> 0000 0110(二进制)

    来得到6。

    因此先移位来取符号位,int i = a >> 31;要注意如果a为正数,i等于0,为负数,i等于-1。然后对i进行判断——如果i等于0,直接返回。否之,返回~a+1。完整代码如下:

    1 //by MoreWindows( http://blog.csdn.net/MoreWindows )  
    2 int my_abs(int a)  
    3 {  
    4     int i = a >> 31;  
    5     return i == 0 ? a : (~a + 1);  
    6 }  

      现在再分析下。对于任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反。因此,a与i异或后再减i(因为i为0或-1,所以减i即是要么加0要么加1)也可以得到绝对值。所以可以对上面代码优化下:

    1 //by MoreWindows( http://blog.csdn.net/MoreWindows )  
    2 int my_abs(int a)  
    3 {  
    4     int i = a >> 31;  
    5     return ((a ^ i) - i);  
    6 }  

    注意这种方法没用任何判断表达式,而且有些笔面试题就要求这样做,因此建议读者记住该方法(^_^讲解过后应该是比较好记了)

    2.5 位操作与空间压缩

    筛素数法在这里不就详细介绍了,本文着重对筛素数法所使用的素数表进行优化来减小其空间占用。要压缩素数表的空间占用,可以使用位操作。下面是用筛素数法计算100以内的素数示例代码(注2):

     1 //by MoreWindows( http://blog.csdn.net/MoreWindows )  
     2 #include <stdio.h>  
     3 #include <memory.h>  
     4 const int MAXN = 100;  
     5 bool flag[MAXN];  
     6 int primes[MAXN / 3], pi;  
     7 //对每个素数,它的倍数必定不是素数。  
     8 //有很多重复如flag[10]会在访问flag[2]和flag[5]时各访问一次  
     9 void GetPrime_1()  
    10 {  
    11     int i, j;  
    12     pi = 0;  
    13     memset(flag, false, sizeof(flag));  
    14     for (i = 2; i < MAXN; i++)  
    15         if (!flag[i])  
    16         {  
    17             primes[pi++] = i;  
    18             for (j = i; j < MAXN; j += i)  
    19                 flag[j] = true;  
    20         }  
    21 }  
    22 void PrintfArray()  
    23 {  
    24     for (int i = 0; i < pi; i++)  
    25         printf("%d ", primes[i]);  
    26     putchar('
    ');  
    27 }  
    28 int main()  
    29 {  
    30     printf("用筛素数法求100以内的素数
    -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --
    
    ");    
    31     GetPrime_1();  
    32     PrintfArray();  
    33     return 0;  
    34 }  

    运行结果如下:

    在上面程序是用bool数组来作标记的,bool型数据占1个字节(8位),因此用位操作来压缩下空间占用将会使空间的占用减少八分之一。

    下面考虑下如何在数组中对指定位置置1,先考虑如何对一个整数在指定位置上置1。对于一个整数可以通过将1向左移位后与其相或来达到在指定位上置1的效果,代码如下所示:

    1 //在一个数指定位上置1  
    2 int j = 0;  
    3 j |=  1 << 10;  
    4 printf("%d
    ", j);  

    同样,可以1向左移位后与原数相与来判断指定位上是0还是1(也可以将原数右移若干位再与1相与)。

    1  //判断指定位上是0还是1  
    2 int j = 1 << 10;  
    3 if ((j & (1 << 10)) != 0)  
    4     printf("指定位上为1");  
    5 else  
    6     printf("指定位上为0");  

    扩展到数组上,我们可以采用这种方法,因为数组在内存上也是连续分配的一段空间,完全可以“认为”是一个很长的整数。先写一份测试代码,看看如何在数组中使用位操作:

     1 //by MoreWindows( http://blog.csdn.net/MoreWindows )    
     2 #include <stdio.h>  
     3 int main()  
     4 {  
     5     printf("     对数组中指定位置上置位和判断该位
    ");  
     6     printf("--- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---
    
    ");  
     7     //在数组中在指定的位置上写1  
     8     int b[5] = {0};  
     9     int i;  
    10     //在第i个位置上写1  
    11     for (i = 0; i < 40; i += 3)  
    12         b[i / 32] |= (1 << (i % 32));  
    13     //输出整个bitset  
    14     for (i = 0; i < 40; i++)  
    15     {  
    16         if ((b[i / 32] >> (i % 32)) & 1)  
    17             putchar('1');  
    18         else   
    19             putchar('0');  
    20     }  
    21     putchar('
    ');  
    22     return 0;  
    23 }  

    可以看出该数组每3个就置成了1,证明我们上面对数组进行位操作的方法是正确的。因此可以将上面筛素数方法改成使用位操作压缩后的筛素数方法:

     1 //使用位操作压缩后的筛素数方法  
     2 //by MoreWindows( http://blog.csdn.net/MoreWindows )   
     3 #include <stdio.h>  
     4 #include <memory.h>  
     5 const int MAXN = 100;  
     6 int flag[MAXN / 32];  
     7 int primes[MAXN / 3], pi;  
     8 void GetPrime_1()  
     9 {  
    10     int i, j;  
    11     pi = 0;  
    12     memset(flag, 0, sizeof(flag));  
    13     for (i = 2; i < MAXN; i++)  
    14         if (!((flag[i / 32] >> (i % 32)) & 1))  
    15         {  
    16             primes[pi++] = i;  
    17             for (j = i; j < MAXN; j += i)  
    18                 flag[j / 32] |= (1 << (j % 32));  
    19         }  
    20 }  
    21 void PrintfArray()  
    22 {  
    23     for (int i = 0; i < pi; i++)  
    24         printf("%d ", primes[i]);  
    25     putchar('
    ');  
    26 }  
    27 int main()  
    28 {  
    29     printf("用位操作压缩后筛素数法求100以内的素数
    -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --
    
    ");    
    30     GetPrime_1();  
    31     PrintfArray();  
    32     return 0;  
    33 }  

      另外,还可以使用C++ STL中的bitset类来作素数表。筛素数方法在笔试面试出现的几率还是比较大的,能写出用位操作压缩后的筛素数方法无疑将会使你的代码脱颖而出,因此强烈建议读者自己亲自动手实现一遍,平时多努力,考试才不慌。

    2.6 高低位交换

    给出一个16位的无符号整数。称这个二进制数的前8位为“高位”,后8位为“低位”。现在写一程序将它的高低位交换。例如,数34520用二进制表示为:

          10000110 11011000

    将它的高低位进行交换,我们得到了一个新的二进制数:

          11011000 10000110

    它即是十进制的55430。

    这个问题用位操作解决起来非常方便,设x=34520=10000110 11011000(二进制) 由于x为无符号数,右移时会执行逻辑右移即高位补0,因此x右移8位将得到0000000010000110。而x左移8位将得到11011000 00000000。可以发现只要将x>>8与x<<8这两个数相与就可以得到11011000 10000110。用代码实现非常简洁:

     1 //高低位交换 by MoreWindows( http://blog.csdn.net/MoreWindows )    
     2 #include <stdio.h>  
     3 template <class T>  
     4 void PrintfBinary(T a)  
     5 {  
     6     int i;  
     7     for (i = sizeof(a) * 8 - 1; i >= 0; --i)  
     8     {  
     9         if ((a >> i) & 1)  
    10             putchar('1');  
    11         else   
    12             putchar('0');  
    13         if (i == 8)  
    14             putchar(' ');  
    15     }  
    16     putchar('
    ');  
    17 }  
    18 int main()  
    19 {  
    20     printf("高低位交换 --- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---
    
    ");  
    21   
    22     printf("交换前:    ");  
    23     unsigned short a = 3344520;  
    24     PrintfBinary(a);  
    25   
    26     printf("交换后:    ");  
    27     a = (a >> 8) | (a << 8);  
    28     PrintfBinary(a);  
    29     return 0;  
    30 }  

    2.7 二进制逆序

    我们知道如何对字符串求逆序,现在要求计算二进制的逆序,如数34520用二进制表示为:

          10000110 11011000

    将它逆序,我们得到了一个新的二进制数:

          00011011 01100001

    它即是十进制的7009。

        回顾下字符串的逆序,可以从字符串的首尾开始,依次交换两端的数据。在二进制逆序我们也可以用这种方法,但运用位操作的高低位交换来处理二进制逆序将会得到更简洁的方法。类似于归并排序的分组处理,可以通过下面4步得到16位数据的二进制逆序:

    第一步:每2位为一组,组内高低位交换

          10 00 01 10  11 01 10 00

      -->01 00 10 01 11 10 01 00

    第二步:每4位为一组,组内高低位交换

          0100 1001 1110 0100

      -->0001 0110 1011 0001

    第三步:每8位为一组,组内高低位交换

          00010110 10110001

      -->01100001 00011011

    第四步:每16位为一组,组内高低位交换

          01100001 00011011

      -->00011011 01100001

    对第一步,可以依次取出每2位作一组,再组内高低位交换,这样有点麻烦,下面介绍一种非常有技巧的方法。先分别取10000110 11011000的奇数位和偶数位,空位以下划线表示。

          原 数   10000110 11011000

          奇数位 1_0_0_1_ 1_0_1_0_

          偶数位 _0_0_1_0 _1_1_0_0

    将下划线用0填充,可得

          原 数   10000110 11011000

          奇数位 10000010 10001000

          偶数位 00000100 01010000

    再将奇数位右移一位,偶数位左移一位,此时将这两个数据相与即可以达到奇偶位上数据交换的效果了。

          原 数          10000110 11011000

          奇数位右移 01000011 01101100

          偶数位左移 0000100 010100000

          相与得到     01001000 11100100

    可以看出,结果完全达到了奇偶位的数据交换,再来考虑代码的实现——

          取x的奇数位并将偶数位用0填充用代码实现就是x & 0xAAAA

          取x的偶数位并将奇数位用0填充用代码实现就是x & 0x5555

    因此,第一步就用代码实现就是:

           x = ((x & 0xAAAA) >> 1) | ((x & 0x5555) << 1);

    类似可以得到后三步的代码。完整程序如下:

     1 //二进制逆序 by MoreWindows( http://blog.csdn.net/MoreWindows )    
     2 #include <stdio.h>  
     3 template <class T>  
     4 void PrintfBinary(T a)  
     5 {  
     6     int i;  
     7     for (i = sizeof(a) * 8 - 1; i >= 0; --i)  
     8     {  
     9         if ((a >> i) & 1)  
    10             putchar('1');  
    11         else   
    12             putchar('0');  
    13         if (i == 8)  
    14             putchar(' ');  
    15     }  
    16     putchar('
    ');  
    17 }  
    18 int main()  
    19 {  
    20     printf("二进制逆序 --- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---
    
    ");  
    21   
    22     printf("逆序前:    ");  
    23     unsigned short a = 34520;  
    24     PrintfBinary(a);  
    25   
    26     printf("逆序后:    ");   
    27     a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1);  
    28     a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2);  
    29     a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4);  
    30     a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8);  
    31     PrintfBinary(a);  
    32 }  

    2.8 二进制中1的个数

    统计二进制中1的个数可以直接移位再判断,当然像《编程之美》书中用循环移位计数或先打一个表再计算都可以。本文详细讲解一种高效的方法。以34520为例,可以通过下面四步来计算其二进制中1的个数二进制中1的个数。

    第一步:每2位为一组,组内高低位相加

          10 00 01 10  11 01 10 00

      -->01 00 01 01  10 01 01 00

    第二步:每4位为一组,组内高低位相加

          0100 0101 1001 0100

      -->0001 0010 0011 0001

    第三步:每8位为一组,组内高低位相加

          00010010 00110001

      -->00000011 00000100

    第四步:每16位为一组,组内高低位相加

          00000011 00000100

      -->00000000 00000111

    这样最后得到的00000000 00000111即7即34520二进制中1的个数。类似上文中对二进制逆序的做法不难实现第一步的代码:

           x = ((x & 0xAAAA) >> 1) + (x & 0x5555);

    好的,有了第一步,后面几步就请读者完成下吧,先动动笔再看下面的完整代码:

     1 //二进制中1的个数  by MoreWindows( http://blog.csdn.net/MoreWindows )   
     2 #include <stdio.h>  
     3 template <class T>  
     4 void PrintfBinary(T a)  
     5 {  
     6     int i;  
     7     for (i = sizeof(a) * 8 - 1; i >= 0; --i)  
     8     {  
     9         if ((a >> i) & 1)  
    10             putchar('1');  
    11         else   
    12             putchar('0');  
    13         if (i == 8)  
    14             putchar(' ');  
    15     }  
    16     putchar('
    ');  
    17 }  
    18 int main()  
    19 {  
    20     printf("二进制中1的个数 --- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---
    
    ");  
    21       
    22     unsigned short a = 34520;  
    23     printf("原数    %6d的二进制为:  ", a);  
    24     PrintfBinary(a);  
    25       
    26     a = ((a & 0xAAAA) >> 1) + (a & 0x5555);  
    27     a = ((a & 0xCCCC) >> 2) + (a & 0x3333);  
    28     a = ((a & 0xF0F0) >> 4) + (a & 0x0F0F);  
    29     a = ((a & 0xFF00) >> 8) + (a & 0x00FF);     
    30     printf("计算结果%6d的二进制为:  ", a);     
    31     PrintfBinary(a);  
    32     return 0;  
    33 }  

    2.9 缺失的数字

      很多成对出现数字保存在磁盘文件中,注意成对的数字不一定是相邻的,如2, 3, 4, 3, 4, 2……,由于意外有一个数字消失了,如何尽快的找到是哪个数字消失了?

      由于有一个数字消失了,那必定有一个数只出现一次而且其它数字都出现了偶数次。用搜索来做就没必要了,利用异或运算的两个特性——1.自己与自己异或结果为0,2.异或满足交换律。因此我们将这些数字全异或一遍,结果就一定是那个仅出现一个的那个数。 示例代码如下:

     1 copy 
     2 //缺失的数字  by MoreWindows( http://blog.csdn.net/MoreWindows )   
     3 #include <stdio.h>  
     4 int main()  
     5 {  
     6     printf("缺失的数字 --- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---
    
    ");  
     7       
     8     const int MAXN = 15;  
     9     int a[MAXN] = {1, 347, 6, 9, 13, 65, 889, 712, 889, 347, 1, 9, 65, 13, 712};  
    10     int lostNum = 0;  
    11     for (int i = 0; i < MAXN; i++)  
    12         lostNum ^= a[i];  
    13     printf("缺失的数字为:  %d
    ", lostNum);     
    14     return 0;  
    15 }  

    2.10 判断奇偶数

    判断一个数是基于还是偶数,相信很多人都做过,一般的做法的代码如下

    1 if( n % 2) == 01
    2     // n 是个奇数
    3 }
    4 

    如果把 n 以二进制的形式展示的话,其实我们只需要判断最后一个二进制位是 1 还是 0 就行了,如果是 1 的话,代表是奇数,如果是 0 则代表是偶数,所以采用位运算的方式的话,代码如下

    1 if(n & 1 == 1){
    2     // n 是个奇数。
    3 }
    4 

      有人可能会说,我们写成 n % 2 的形式,编译器也会自动帮我们优化成位运算啊,这个确实,有些编译器确实会自动帮我们优化。但是,我们自己能够采用位运算的形式写出来,当然更好了。别人看到你的代码,我靠,牛逼啊。无形中还能装下逼,是不是。当然,时间效率也快很多,不信你去测试测试。

    2.11 3的n次方

    如果让你求解 3 的 n 次方,并且不能使用系统自带的 pow 函数,你会怎么做呢?这还不简单,连续让 n 个 3 相乘就行了,代码如下:

    1 int pow(int n){
    2     int tmp = 1;
    3     for(int i = 1; i <= n; i++) {
    4         tmp = tmp * 3;
    5     }
    6     return tmp;
    7 }

    不过你要是这样做的话,我只能呵呵,时间复杂度为 O(n) 了,怕是小学生都会!如果让你用位运算来做,你会怎么做呢?

    我举个例子吧,例如 n = 13,则 n 的二进制表示为 1101, 那么 3 的 13 次方可以拆解为:

      3^1101 = 3^0001 * 3^0100 * 3^1000。

    我们可以通过 & 1和 >>1 来逐位读取 1101,为1时将该位代表的乘数累乘到最终结果。直接看代码吧,反而容易理解:

     1 int pow(int n){
     2     int sum = 1;
     3     int tmp = 3;
     4     while(n != 0){
     5         if(n & 1 == 1){
     6             sum *= tmp;
     7         }
     8         tmp *= tmp;
     9         n = n >> 1;
    10     }
    11     
    12     return sum;
    13 }

    2.12 找出不大于N的最大的2的幂指数

    传统的做法就是让 1 不断着乘以 2,代码如下:

     1 int findN(int N){
     2     int sum = 1;
     3    while(true){
     4         if(sum * 2 > N){
     5             return sum;
     6         }
     7         sum = sum * 2;
     8    }
     9 }

    这样做的话,时间复杂度是 O(logn),那如果改成位运算,该怎么做呢?我刚才说了,如果要弄成位运算的方式,很多时候我们把某个数拆成二进制,然后看看有哪些发现。这里我举个例子吧。

    例如 N = 19,那么转换成二进制就是 00010011(这里为了方便,我采用8位的二进制来表示)。那么我们要找的数就是,把二进制中最左边的 1 保留,后面的 1 全部变为 0。即我们的目标数是 00010000。那么如何获得这个数呢?相应解法如下:

    1、找到最左边的 1,然后把它右边的所有 0 变成 1

    2、把得到的数值加 1,可以得到 00100000即 00011111 + 1 = 00100000。

    3、把 得到的 00100000 向右移动一位,即可得到 00010000,即 00100000 >> 1 = 00010000。

    那么问题来了,第一步中把最左边 1 中后面的 0 转化为 1 该怎么弄呢?我先给出代码再解释吧。下面这段代码就可以把最左边 1 中后面的 0 全部转化为 1,

    1 n |= n >> 1;
    2 n |= n >> 2;
    3 n |= n >> 4;

      就是通过把 n 右移并且做或运算即可得到。我解释下吧,我们假设最左边的 1 处于二进制位中的第 k 位(从左往右数),那么把 n 右移一位之后,那么得到的结果中第 k+1 位也必定为 1,然后把 n 与右移后的结果做或运算,那么得到的结果中第 k 和 第 k + 1 位必定是 1;同样的道理,再次把 n 右移两位,那么得到的结果中第 k+2和第 k+3 位必定是 1,然后再次做或运算,那么就能得到第 k, k+1, k+2, k+3 都是 1,如此往复下去....

    最终的代码如下

    1 int findN(int n){
    2     n |= n >> 1;
    3     n |= n >> 2;
    4     n |= n >> 4;
    5     n |= n >> 8 // 整型一般是 32 位,上面我是假设 8 位。
    6     return (n + 1) >> 1;
    7 }

    这种做法的时间复杂度近似 O(1)。

    2.13 不用+完成加法的算法:

     1     int aplusb(int a, int b) {
     2         while (b) {
     3             int a1 = a ^ b;
     4             int b1 = (a & b) << 1;
     5             a = a1;
     6             b = b1;
     7         }
     8         
     9         return a;    
    10     }

    以a=3 (0011), b=5(0101)为例。
    a = 0011 => 0110 => 0100 => 0000 =>1000 (return) //未进位加法和
    b = 0101 => 0010 =>0100 => 1000 =>0000 //进位

    递归版本如下:

    1 int aplusb(int a, int b)  
    2 {  
    3     if (a == 0)  return b;  
    4     if (b == 0)  return a;  
    5     return aplusb((a & b) << 1, a ^ b);
    6 }  

    2.14 计算a要反转多少位变成b

    1     int bitSwapRequired(int a, int b) {
    2         int c = a ^ b;
    3         int count = 0;
    4         while (c) {
    5             count++;
    6             c &= c - 1;
    7         }
    8         return count;
    9     }

    2.15 计算一个32位整数有多少个1

    1 int countOnes(int num) {
    2      int count = 0;
    3      while (num) {
    4          count++;
    5          num &= num - 1;
    6      }
    7      return count;
    8 }

    三、位运算性质

    3.1 异或运算满足以下性质

      1. x^x = 0

      2. x^y = y^x

      3. (x^y)^z = x^(y^z)

      4. x^y^y = x

      5. 任意正整数i,4*i ^ (4*i + 1) ^ (4*i + 2) ^ (4*i + 3) = 0

    四、下面列举一些常见的二进制位的变换操作

     1 功能                示例         位运算
     2 去掉最后一位    (101101->10110) x >> 1
     3 在最后加一个0    (101101->1011010)   x < < 1
     4 在最后加一个1    (101101->1011011)   x < < 1+1
     5 把最后一位变成1    (101100->101101)    x | 1
     6 把最后一位变成0    (101101->101100)    x | 1-1
     7 最后一位取反    (101101->101100)    x ^ 1
     8 把右数第k位变成1    (101001->101101,k=3)    x | (1 < < (k-1))
     9 把右数第k位变成0    (101101->101001,k=3)    x & ~ (1 < < (k-1))
    10 右数第k位取反    (101001->101101,k=3)    x ^ (1 < < (k-1))
    11 取末三位    (1101101->101)  x & 7
    12 取末k位    (1101101->1101,k=5) x & ((1 < < k)-1)
    13 取右数第k位    (1101101->1,k=4)    x >> (k-1) & 1
    14 把末k位变成1    (101001->101111,k=4)    x | (1 < < k-1)
    15 末k位取反    (101001->100110,k=4)    x ^ (1 < < k-1)
    16 把右边连续的1变成0    (100101111->100100000)  x & (x+1)
    17 把右起第一个0变成1    (100101111->100111111)  x | (x+1)
    18 把右边连续的0变成1    (11011000->11011111)    x | (x-1)
    19 取右边连续的1    (100101111->1111)   (x ^ (x+1)) >> 1
    20 去掉右起第一个1的左边    (100101000->1000)   x & (x ^ (x-1))
    21 判断奇数        (x&1)==1
    22 判断偶数        (x&1)==0
    23 取左右边的1    x^(x - 1)
    24 消去最右边的1    x&(x - 1)   
    25 (x - 2) ^ (x - 1) ^ x = x + 1

    本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/14473156.html

  • 相关阅读:
    Git和Github的基本操作
    整合Flask中的目录结构
    自定义Form组件
    flask-script组件
    flask-session组件
    flask中的wtforms使用
    补充的flask实例化参数以及信号
    用flask实现的分页
    用flask的扩展实现的简单的页面登录
    【字符串】【扩展kmp算法总结~~】
  • 原文地址:https://www.cnblogs.com/MrLiuZF/p/14473156.html
Copyright © 2011-2022 走看看