算法技巧-位运算(Bit Manipulation)

算法技巧-位运算(Bit Manipulation)

  • 一 . 位运算简介
  • 二 . 位运算规则
    • A . AND与运算
    • B . OR或运算
    • C . XOR异或运算
    • D . NOT取反运算
    • E . 位移运算
  • 三 . 位运算技巧
    • A . AND与运算技巧
      • 1 . 清零
      • 2 . 取指定二进制位
      • 3 . 判断奇偶性
      • 4 . 判断是否为2的次方
    • B . OR或运算技巧
      • 1 . 置位
    • C . XOR异或运算技巧
      • 1 . 翻转指定二进制位
      • 2 . 保留原值
      • 3 . 异或为零
      • 4 . 运算律及自反性
      • 5 . 交换两数
    • D . NOT取反运算技巧
      • 1 . 变换符号
      • 2 . 取绝对值
    • E . 位移运算技巧
      • 1 . 乘2的N次方
      • 2 . 除2的N次方
    • F . 综合运算技巧
  • 四 . 位运算例题
    • A . LeetCode
      • 1 . 第136题
      • 2 . 第268题
      • 3 . 第191题
      • 4 . 第231题
      • 5 . 第371题
  • 五 . 位运算资料
    • A . 关于取反运算
    • B . 位运算总结帖
    • C . 位运算技巧说明帖

一 . 位运算简介

首先看下Wiki百科对位运算的定义:
  位运算是一种通过算法来处理比特或比词短的数据片段的操作。需要位运算的计算机程序任务包括低级设备控制、错误检测和校正算法、数据压缩、加密算法和优化。对于大多数其他任务,现代程序语言允许编程者直接在抽象层面上工作,而不是去操作那些抽象的比特位(即我们现在编程都是用代码写程序,而不是0和1)。在某些情况下,位运算可以避免或者减少在一个数据结构上需要进行循环的次数,并且可以成倍的提升效率,因为位运算是并行处理的,但是缺点是位运算的代码比较难以编写及维护。

二 . 位运算规则

在C/C++中可以用的位运算包括:ANDORXORNOT位移

A . AND与运算

与运算操作符为&,其运算规则为:

操作数一 操作数二 RESULT
0 0 0
0 1 0
1 0 0
1 1 1

B . OR或运算

或运算操作符为|,其运算规则为:

操作数一 操作数二 RESULT
0 0 0
0 1 1
1 0 1
1 1 1

C . XOR异或运算

异或运算操作符为^,其运算规则为:

操作数一 操作数二 RESULT
0 0 0
0 1 1
1 0 1
1 1 0

D . NOT取反运算

取反运算操作符为~,其运算规则为:

操作数 RESULT
0 1
1 0

注意事项:
  由于在计算机中,二进制的存储形式为补码形式,事实上,计算机中有且仅有补码一种码,而我们所说的原码、反码都是所定义出的为了与补码形成转换关系的格式。正数的原码、反码、补码都是其本身,负数的原码最高位符号位为1,反码除符号位外取反,补码在反码基础上加一得到。例如,正整数5的原码、反码、补码都为0x00000101,对5取反就有0x11111010,由于这是补码表示,转换为反码是0x11111001,转换为原码是0x10000110,即-6为对5取反后的值。

E . 位移运算

位移运算包括左移(<<)与右移(>>),其运算规则为:
  当进行左移操作时,若高位溢出则舍弃,低位空出用0填补;当进行右移操作时,若低位溢出则舍弃,高位空出,正数用0填补,负数用1填补。

三 . 位运算技巧

A . AND与运算技巧

1 . 清零

如果想将一个指定单元清零,可以通过&0实现,即:

int num = 1; /* Clear the num. */
int res = num & 0; /* Now num is zero. */

2 . 取指定二进制位

如果想取某一个数的指定二进制位,可以通过&1操作实现,例如要保存最低两位的二进制位:

int num = 10; /* num = 00001010 */
int res = num & 3; /* 3 = 00000011 res = 00000010 */

3 . 判断奇偶性

如果想要判断一个数的奇偶性,可以利用其最低位是0还是1来判断:

int num = 10; /* num = 00001010 */
if((num & 1) == 0) printf("这是偶数!");

4 . 判断是否为2的次方

如果需要判断一个数是否为2的次方数,那么只要该数的二进制形式中1的个数为1:

int num = 8; /* num = 00001000 */
if(num > 0 && (num & (num - 1)) == 0) printf("这是2的次方数!");

需要注意的是,这种用法n&(n-1)十分常见,其作用是将n二进制中最右一位1置0,例如寻找某数二进制表示中有几个1,可以通过n&(n-1)将最右的1置0直到值为0时求得结果。

B . OR或运算技巧

1 . 置位

如果想将一个数的某一二进制位置1,可以通过|1操作实现,例如要将最高两位二进制位置1:

int num = 2; /* num = 00000010 */
int res = num | 24; /* 24 = 00011000 res = 00011010 */

C . XOR异或运算技巧

1 . 翻转指定二进制位

如果想将一个数的某些二进制位翻转,可以通过^1操作实现,例如要将偶数位翻转:

int num = 51; /* num = 01100110 */
int res = num ^ 42; /* 42 = 01010101 res = 00110010 */

2 . 保留原值

异或运算有一个特性为,当某数^0时,仍为原值:

int num = 18; /* num = 00100100 */
int res = num ^ 0; /* res = 00100100 */

3 . 异或为零

异或运算有一个特性为,当某数^自身时,其值为0:

int num = 18; /* num = 00100100 */
int res = num ^ num; /* res = 00000000 */

4 . 运算律及自反性

异或支持交换律:a ^ b = b ^ a
异或支持结合律:(a ^ b) ^ c = a ^ (b ^ c)
通过异或特性2、3及运算律可以推出其自反性:a ^ b ^ b = a ^ (b ^ b) = a ^ 0 = a

5 . 交换两数

我们通常会声明一个中间变量辅助完成两数交换的操作,但是也可以不使用中间变量来做:

int a = 18; /* a = 00100100 */
int b = 27; /* b = 00110110 */
a = a ^ b; /* a = 00010010 */
b = b ^ a; /* b = 00100100 */
a = a ^ b; /* a = 00110110 Now a = 27 b = 18. */

补充一个非位运算的交换方法:

int a = 18;
int b = 27;
a = a + b; /* a = a + b */
b = a - b; /* b = a + b - b = a */
a = a - b; /* a = a + b - a = b */

D . NOT取反运算技巧

1 . 变换符号

通常我们变换符号会使用0减去某数的方式,但是通过位运算也可以做到:

int num = 5;
int res = ~num + 1; /* res = -5 */

按位取反的快捷公式为:~num=-(num+1)

2 . 取绝对值

通过取反操作可以取绝对值:

int num_one = 12;
int num_two = -15;
if ((num_one >> 31) == 0 ? res = num_one : res = (~num_one + 1));
if ((num_two >> 31) == 0 ? res = num_two : res = (~num_two + 1));

对任何数,与0异或不会变化,与-1异或相当于取反(注意,计算机中用补码存储),因此,num与mask异或(正数不变,负数取反)后再减去mask(正数减0,负数减-1),即另一种方法:

int num = -10;
int mask = num >> 31;
int res = (num ^ mask) - mask;

E . 位移运算技巧

1 . 乘2的N次方

若左移时舍弃的高位不包含1,则每左移一位,相当于乘2:

int num = 5;
int res = num << 2; /* res = 20 */

2 . 除2的N次方

每右移一位,相当于除2:

int num = 20;
int res = num >> 2; /* res = 4 */

F . 综合运算技巧

四 . 位运算例题

A . LeetCode

1 . 第136题

给定一个数组,有且仅有一个值出现了1次,其余均出现了2次,找出其中出现1次的值:

int singleNumber(vector<int>& nums) {
	int res = 0;
	for(int i = 0; i < nums.size(); i++){
		res ^= nums[i];
	}
	return res;
}

题目链接

2 . 第268题

给定一个值为0到n的数组,找出其中缺失的某个数字:

int missingNumber(vector<int>& nums) {
	int res = 0;
	for(int i = 0; i < nums.size(); i++){
		res ^= (i + 1) ^ nums[i];
	}
	return res;
}

题目链接

3 . 第191题

实现一个函数,可以返回无符号整型数中比特位为1的个数(又称:Hamming Weight):

int hammingWeight(uint32_t n) {
	int num = 0;
	while(n != 0) {
		n &= (n - 1);
		num++;
	}
	return num;
}

题目链接

4 . 第231题

实现一个函数用于判断某数是否为2的次方:

bool isPowerOfTwo(int n) {
	if(n <= 0) return false;
	if(n == 1) return true;
	int res = n & (n - 1);
	if(res == 0) return true;
	else return false;
}

题目链接

5 . 第371题

不使用+或-号完成两数相加的操作:

int getSum(int a, int b) {
	return b == 0 ? a : getSum(a ^ b, (unsigned int)(a & b) << 1);
}

题目链接

五 . 位运算资料

A . 关于取反运算

按位取反运算解析
按位取反快捷公式推理证明

B . 位运算总结帖

LeeCode-LHearen
位运算总结
位运算小结
位运算在算法中的应用小结
位运算一些例题总结
优秀程序员不得不知道的20个位运算技巧
位运算常用技巧总结
算法技巧-带你领略位运算的魅力
位运算技巧总结
位运算简介及实用技巧一:基础篇
位运算简介及实用技巧二:进阶篇上
位运算简介及实用技巧三:进阶篇下
位运算简介及实用技巧四:实战篇
知乎话题:位运算有什么奇技淫巧

C . 位运算技巧说明帖

位运算求两数平均值

你可能感兴趣的:(数据结构与算法,算法技巧)