小白算法学习 1 --位与二进制
基础题部分
按位异或 ^ 的用处
- 使特定位翻转
比如:01111010,想使其低4位翻转,即1变为0,0变为1。可以将它与00001111进行∧运算,要使哪几位翻转就将与其∧运算的该几位置为1即可。
- 通过按位异或运算,可以实现两个值的交换,而不必使用临时变量。
例如交换两个整数a=3,b=4的值,可通过下列语句实现:
a=a∧b;
b=b∧a;
a=a∧b;
位运算的妙用
- 位运算实现加法:
x^y :相加
(x&y)<<1 :移位进位
//位运算实现加减法
#include <stdio.h>
int add(int a, int b)
{
if(b==0)
return a;
else
{
int sum = a^b; //按位异或
int carry = (a&b)<<1; //按位与 左移
return add(sum,carry);
}
}
int negative(int a) {
return ~a + 1;
}
int Minus(int a, int b) {
return add(a, negative(b));
}
int main()
{
int a = 10, b = 15;
printf("%d\n",add(a,b));
printf("%d",Minus(a,b));
return 0;
}
- 位运算实现除法:(乘除同理)
除法可以缩小步长:
计算机是一个二元的世界,所有的int型数据都可以用[2^0, 21,…,2{31}]这样一组基来表示(int型最高31位)。不难想到用除数的2{31},2{30},…,22,21,2^0倍尝试去减被除数,如果减得动,则把相应的倍数加到商中;如果减不动,则依次尝试更小的倍数。这样就可以快速逼近最终的结果。
int divide(int a, int b) {
//结果溢出:返回int最大值
if (a == INT_MIN&&b == -1)
{
return INT_MAX;
}
unsigned int ans = 0;
int sign = (a > 0) ^ (b > 0) ? -1 : 1; //使用异或记录结果的符号,只有一正一负才为负号
unsigned int ua = abs(a);//使用无符号数字避免越界
unsigned int ub = abs(b);//使用无符号数字避免越界
for (int i = 31; i >= 0; --i)//从最高位开始以为,减少运算
{
if ((ua >> i) >= ub)//如果ua右移i位仍大于ub,则ub左移i位一定小于ua,即可以被减
{
ua = ua - (ub << i);//减去之后再次对ua进行运算
ans += 1 << i;//记录当前减去多少次ub
}
}
return sign == 1 ? ans : -ans;
}
//二进制求相反数需要按位取反再加一
提高题部分
Brian Kernighan 算法
该算法可以被描述为这样一个结论:记 f(x) 表示 x 和 x-1 进行与运算所得的结果(即 f(x)=x & (x−1)),那么f(x) 恰为 x 删去其二进制表示中最右侧的 1 的结果。
例题1:
var hammingDistance = function(x, y) {
let s = x ^ y, ret = 0;
while(s != 0){
s &= s - 1;
ret++;
}
return ret;
};
例题2:计算从 00 到 nn 的每个整数的二进制表示中的 11 的数目
利用Brian Kernighan算法,可以在一定程度上进一步提升计算速度。Brian Kernighan算法的原理是:对于任意整数 x,令 x=x & (x−1),该运算将 x 的二进制表示的最后一个 1 变成 0。因此,对 x 重复该操作,直到 x 变成 0,则操作次数即为 x 的「一比特数」。
对于给定的 nn,计算从 00 到 nn 的每个整数的「一比特数」的时间都不会超过 O(logn),因此总时间复杂度为 O(nlogn)。
int count(int x){
int co=0;
while(x>0){
x &= x-1;
co++;
}
return co;
}
vector<int> countBits(int n) {
vector<int> Bits(n+1);
for(int i=0;i<=n;i++){
Bits[i]=count(i);
}
return Bits;
}
例题3:给定整数n,判断其是否为2的幂次方
var isPowerOfTwo = function(n) {
return n > 0 && (n&(n-1)) == 0;
};//js