您的位置:首页 > 职场人生

剑指Offer之面试位运算总结

2013-11-05 17:50 253 查看

(1)基本知识

位运算的题目经常出现在面试中。在此总结一下关于位运算的知识点。

位运算是把数字用于二进制表示之后,对每一位上的0,1的运算。

其实二进制的位运算并不是很难掌握,因为位运算总共只有五种运算:与,或,异或,左移和右移。



左移:

左移运算符m <<n 表示把m左移n位。在左移n位的时候,最左边的n位将被丢弃,同时在最右边补上n个0。

比如:

00001010 << 2 = 00101000

10001010 << 3 = 01010000

右移:

右移运算符m >> n表示把m右移n位。右移n位的时候最右边的n位将被丢弃。但注意的是右移时候的最左边的n位的处理。

如果数字是一个为无符号数值,则用0填补最左边的n位。如果数字是一个有符号数值,则用数字的符号位填补最左边的n位。

也就是说如果原先数字是一个整数则右移之后最左边补上n个0,;如果数字原始是负数,则右移之后在左边补n个1。

比如:

00001010 >> 2 = 00000010

10001010 >> 3 = 11110001

注意以下几点:

1.在这5种操作符,都是双目操作符。只有~取反是单目操作符,在这没有提及。

2.位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错。

3.对于移位操作,在微软的VC6.0和VS2008编译器都是采取算术称位即算术移位操作,算术移位是相对于逻辑移位,它们在左移操作中都一样,低位补0即可,但在右移中逻辑

移位的高位补0而算术移位的高位是补符号位。

4.位操作符的运算优先级比较低,因为尽量使用括号来确保运算顺序,否则很可能会得到莫明其妙的结果。比如要得到像1,3,5,9这些2^i+1的数字。写成int a = 1 << i + 1;是

不对的,程序会先执行i + 1,再执行左移操作。应该写成int a = (1 << i) + 1;

5.另外位操作还有一些复合操作符,如&=、|=、 ^=、<<=、>>=。

(2)位运算常用技巧

1.判断奇偶

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

int a = 4;
	if(a & 1){
		printf("%d是奇数!\n",a);
	}
	else{
		printf("%d是偶数!\n",a);
	}


2.交换数据

//临时变量法交换数据
void swap(int &a, int &b){
	int temp;
	if (a != b){
		temp = a;
		a = b;
		b = temp;
	}
}
//位运算法交换数据
void swap2(int &a, int &b){
	if (a != b){
		a ^= b;  
        b ^= a;  
        a ^= b;  
	}
}


第一步 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=6

3.变换符号

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

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

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

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

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

因此变换符号只需要取反后加1即可。

int a = 4;
	int b = ~a + 1;
	printf("%d\n",b);
	int c = -4;
	int d = ~c + 1;
	printf("%d\n",d);


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。

//取符号位
	int a = -100;
	int i = a >> 31;
	//i = 0 正数
	if(i == 0){
		printf("%d\n",a);
	}
	//i = 1 负数
	else{
		printf("%d\n",~a + 1);
	}


对于任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反。

因此,a与i异或后再减i(因为i为0或-1,所以减i即是要么加0要么加1)也可以得到绝对值。

int a = -100;
	int i = a >> 31;  
    int b = ((a ^ i) - i); 
	printf("%d\n",b);


(3)位运算应用

1.高低位互换

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

它即是十进制的55430。

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

2.判断一个整数是不是2的整数次方

如果一个整数是2的整数次方,那么他的二进制标识中一定有且只有一位是1,而其他所有位均为0.

解决方案:

把这个整数减去1之后再和他自己做与运算,这个整数中唯一的一个1就会变成0.所以只要判断是不是等于0即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: