位运算

1. 面试的题目很多是关于位运算的。位运算包括:与,或,异或,取反,左移,右移。

2.下面是几道位运算的经典题目。

2.1 判断一个数是不是2的密次方?

解析:一个数要是2的密次方,必然其二进制表示只有一位为1。所以题目转换为判读这个数是否只有一位为1??代码如下:

1 bool IsTwoPower(unsigned int x) 
2 {   return !(x & (x-1)); } 

但是上述代码有一个问题,就是当x=0时,返回值也是true,显然0不是2的密次方。代码修改如下:

1 bool IsTwoPower(unsigned int x) 
2 {   return x && !(x & (x-1)); } 

2.2 给定一个正整数,求出其二进制表示中1的个数?

解析:最容易想到的方法,用这个数与1相与,判断结果是否为1,然后这个数右移一位,直到这个数为0为止。时间复杂度为O(k),k为左边第一个1出现的位置。常用的并且高效的方法是上述方法的改变,时间复杂度为O(N),N为1的个数。代码如下:

 1 int NumberOfOne1(unsigned int num)
 2 {
 3     int count=0;
 4     while (num!=0)
 5     {
 6         if (num&1)
 7         {
 8             count++;
 9         }
10         num=num>>1;
11     }
12     return count;
13 }
14 
15 //上面1的改版,上面的算法对整数没问题,但是对负数
16 //会出现死循环,改成下面的形式
17 int ChangeNumberOfOne1(unsigned int num)
18 {
19     unsigned int flag=1;
20     int count=0;
21     while (flag)
22     {
23         if (flag & num)
24         {
25             count++;
26         }
27         flag=flag<<1;
28     }
29     return count;
30 }
31 
32 //改进的算法,也是比较高效的算法
33 //每循环一次,num就会少一个1
34 int NumberOfOne2(unsigned int num)
35 {
36     int count=0;
37     while (num!=0)
38     {
39         count++;
40         num=num & (num-1);
41     }
42     return count;
43 }

2.3 给定一个unsigned int将其按二进制表示反转?

分析:目前出现最多的有两种算法。算法1:类似于数组的反转,每次交换最左边和最右边未交换的数。由于是二进制只有两种可能:要么是0,要么是1。两个位置的数同为0或1时,不需要交换。不同时,即异或结果为1时,才要交换。交换同样使用异或操作,原理:任何数(0或1)和1异或就是取反。算法2:使用的是类似于归并的交换算法。比如要交换:01101101。第一次将奇数位和偶数位交换:10011110,第二次将相邻每两位交换:01101011,第三步将相邻四位交换:10110110。

算法1代码如下:

 1 typedef unsigned int uint;
 2 uint swapBits(uint x, uint i, uint j) {
 3   uint lo = ((x >> i) & 1);
 4   uint hi = ((x >> j) & 1);
 5   if (lo ^ hi) {
 6     x ^= ((1U << i) | (1U << j));
 7   }
 8   return x;
 9 }
10   
11 uint reverseXor(uint x) {
12   uint n = sizeof(x) * 8;
13   for (uint i = 0; i < n/2; i++) {
14     x = swapBits(x, i, n-i-1);
15   }
16   return x;
17 }

 

算法2代码如下:

1 uint reverseMask(uint x) {
2   assert(sizeof(x) == 4); // special case: only works for 4 bytes (32 bits).
3   x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1);
4   x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2);
5   x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4);
6   x = ((x & 0x00FF00FF) << 8) | ((x & 0xFF00FF00) >> 8);
7   x = ((x & 0x0000FFFF) << 16) | ((x & 0xFFFF0000) >> 16);
8   return x;
9 }

2.4 给定一个数组,这个数组中只有一个数出现1次,其余数均出现两次。求出这个数。

解析:使用异或操作。因为两个相同的数异或结果为0,任何数和0异或结果为其本身。代码如下:

 1 int FindOne(int num[],int length)
 2 {
 3     assert(num && length>0);
 4     int result=0;
 5     for (int i=0;i<length;i++)
 6     {
 7         result^=num[i];
 8     }
 9     return result;
10 }

2.5 上一题的变形。给定一个数组,这个数组中只有2个数出现1次,其余数均出现两次。求出这2个数。

解析:有两个数字只出现一次,显然不能像上一题一样直接使用异或运算。但是如果把数组分成两部分,把两个只出现一次的数分到两个部分,在每一个部分中只有一个数出现一次,就可以使用上一题的算法。

我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。我们在结果数字中找到第一个为1的位的位置,记为第N位。现在我们以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0

现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。因此到此为止,所有的问题我们都已经解决。代码如下:

 

 1 #include <iostream>
 2 #include <cassert>
 3 
 4 using namespace std;
 5 
 6 //判断第indexBit是否为1
 7 bool IsBit1(int num,unsigned int indexBit)
 8 {
 9     num=num>>indexBit;
10     return (num & 1);
11 }
12 
13 //找到num中右边第一个1出现的位置
14 unsigned int FindFirstBit1(int num)
15 {
16     int indexBit=0;
17     while (((num & 1)==0) && (indexBit<32))
18     {
19         num=num>>1;
20         ++indexBit;
21     }
22     return indexBit;
23 }
24 
25 void FindNumsAppearOnce(int data[],int length,int& num1,int& num2)
26 {
27     if (length<2)
28     {
29         return;
30     }
31     int resultExclusiveOR=0;
32     for (int i=0;i<length;i++)
33     {
34         resultExclusiveOR^=data[i];
35     }
36     unsigned int indexOf1=FindFirstBit1(resultExclusiveOR);
37     num1=num2=0;
38     for (int j=0;j<length;j++)
39     {
40         if (IsBit1(data[j],indexOf1))
41         {
42             num1^=data[j];
43         }
44         else
45             num2^=data[j];
46     }
47 }
48 
49 int main()
50 {
51     const int length=10;
52     int number[length]={2,3,2,12,12,1234,1234,2345,2345,9999};
53     int num1=0;
54     int num2=0;
55     FindNumsAppearOnce(number,length,num1,num2);
56     cout<<num1<<"  "<<num2<<endl;
57 }

 2.6 下面的代码:

1 int f(int x,int y)
2 {
3     return (x&y)+((x^y)>>1);
4 }

的作用是取x和y的平均数:x&y是把相同的位除以2,(x^y)>>1是将不同的位除以2。相加之后就是取平均数的结果!!!

参考文章:

http://zhedahht.blog.163.com/blog/static/2541117420071128950682/

http://www.leetcode.com/category/bit-operations

http://blog.sina.com.cn/s/blog_63ce05ca0100u0ft.html

《编程之美》

 

 

posted @ 2012-08-16 10:09  kasuosuo  阅读(346)  评论(0编辑  收藏  举报