概述
在学习程序语言时,并没有感受到位运算的用处,只知道左移右移可以代替乘法除法且效率较高。学习完CASPP后,发现其实位运算还是比较重要的,它更贴近于底层,最近写的一些题目中,发现位运算有许多意想不到的作用,可以巧解一些题,在这里做一下总结和梳理。
位运算符号
& : 与运算——按位与,两个位为1则与运算之后为1,其余情况为0
| :或运算——按位或,两个位为0则或运算之后为0,其余情况为1
^ : 异或运算——两个位相同异或为0,否则为1
~ :取反运算——对二进制每个位取反,即0变为1,1变为0
<< : 左移运算——将二进制位向右移,右边补0,n<<1, 即n*2(无符号未溢出)
应用
1、判断一个数的奇偶性
bool is_odd(int x)//返回为true为奇数,false为偶数
{
if(x&1) return true;
else return false;
}
x&1其实就是判断x的二进制最低位是1还是0,若为1,则x&1为1,该数为奇数,反之,x&1为0,则该数为偶数。
2、判断二进制数某位是1或0
bool is_zero(int x,int n)//n为该二进制数,x为第x位,假设该数共32位
{
if(n & (1<<(32-x))) return true;
else return false;
}
其实这个判断和判断奇偶性是类似的,也是判断二进制数某一位是否为0或1,只需要用左移操作移动到判断的哪一位即可。
3、判断某数是否为2的幂次
bool is_twopower(int n)//true为是2的幂
{
if(n&(n-1)==0) return true;
else return false;
}
一个数若为2的幂,则二进制的数中只有一个1,n&(n-1)可以将唯一的1个1消去,故当为0时,则是2的幂。
4、交换数值
void swap(int &a,int &b)
{
a=a^b;
b=a^b;
a=a^b;
}
第一行:a=a^b;
第二行:b=(a^b) ^b, 因为 b ^ b=0,所以b = a^0 = a;
第三行:a=a^b= a^a ^b=0 ^ b=b;
采用一些异或的计算做到了两个数的交换
5、找出不重复的数
首先是最简单的一种情况,只有一个数出现一次,其余数都出现两次。
因为相同的数异或之后为0,故将所有的数都异或的结果就是那个出现一次的数。
int find_data(vector<int>& num)
{
int res=0;
for(int i=0;i<num.size();i++)
{
res=res^num[i];
}
return res;
}
第二种情况,只有一个数出现一次,其余的数都出现三次。
这种情况有两种位操作的解法:
第一种,是将每一个整数用32位表示,记录所有整数每一位是1的个数sum,sum对3取余数,如果余数不为0,则出现一次的那个数对应位上为1,通过左移操作将每一位数移动到相应的位置上。
int find_data(vector<int>& num)
{
int res=0;
for(int i=0;i<32;i++)
{
int sum=0;
for(int j=0;j<num.size();j++)
{
sum+=(num[i]>>i)&1;//计算位1的个数
}
res=res|(sum%3)<<i;//得到出现一次的数相应二进制位上的数
}
return res;
}
这种解法可以解决一串数只有一个数出现一次,其余数出现k次的情况
第二种解法是使用状态机法,用k-1个数表示状态机的k个状态。
在这里有三个状态,其实也就是模3之后余数的个数,0,1,2,用二进制表示分别为00 01 10,由于用二进制的两个位来表示,因此可以用两个变量来表示两个位,普遍来讲,可以使用一个数组来表示各位的数。
int find_data(vector<int>& num)//这里是2个状态的代码,使用变量
{
int bits_one=0,bits_two=0;
for(int i=0;i<num.size();i++)
{
bits_one=(bits_one^num[i])&~bits_two;
bits_two=(bits_two^num[i])&~bits_one;
}
return bits_one;
}
而这里的位操作是非常的复杂和巧妙,我是想不出来……
在这里有大神的具体讲解:LeetCode题解
6、二进制枚举子集
枚举一个集合的幂集,可以采用二进制的算法,将二进制的一位对应集合中一个元素的取或者不取,1表示选取,0表示未选取。
vector<vector<int>> power_set(vector<int> nums)
{
vector<vector<int>> res;
int len=nums.size();
for(int i=0;i<(1<<len);i++)//有1<<len个幂集
{
vector<int> temp;
for(int j=0;j<len;j++)
{
if(((1<<j)&i)!=0) temp.push_back(nums[j]);
//第j位的元素未被选取
}
res.push_back(temp);
}
return res;
}
结语
位操作是一个强大的工具,还有许多的应用等待着我们去发现…………