算法学习(25):位运算
位运算
题目1:a和b返回大的,要求不能有比较
基本思路:利用a-b的差值的符号来判断谁大谁小
不考虑溢出情况的C++代码
int flip(int n); //输入0或1,1变0,0变1
int sign(int n); //拿到n的符号,正数返回1,负数返回0
int getMax(int a, int b)
{
int c = a - b; //不考虑溢出
int sOa = sign(c);
int sOb = flip(sOa);
return sOa * a + sOb * b;
}
int flip(int n)
{
return n ^ 1;
}
int sign(int n)
{
return flip(n >> 31 & 1);
}
考虑溢出情况的C++代码
int flip(int n); //输入0或1,1变0,0变1
int sign(int n); //拿到n的符号,正数返回1,负数返回0
int getMax(int a, int b)
{
int c = a - b;
int sOa = sign(a); //拿到a的符号
int sOb = sign(b); //拿到b的符号
int sOc = sign(c); //拿到c的符号
int difSab = sOa ^ sOb; //a和b符号不同为1,相同为0
int sameSab = flip(difSab); //a和b符号相同为1,不同为0
int returnA = difSab * sOa + sameSab * sOc; //当a和b符号不同时,如果a的符号为正,则returnA为1;当a和b符号相同时,如果c的符号为正,则returnA为1
int returnB = flip(returnA); //返回b的情况是返回A的相反
return returnA * a + returnB * b;
}
int flip(int n)
{
return n ^ 1;
}
int sign(int n)
{
return flip(n >> 31 & 1);
}
题目2:判断一个数是否是2的幂或4的幂
是否是2的幂思路:
- 一个数如果是2的幂,则它的二进制数只能有一个1,拿到最右侧的1,然后跟原来的数比较,相等则是2的幂
- 一个数如果是2的幂,那么它减去1,会让原来二进制是1的那一位变成0,后面的所有位都变成1,例如8的二进制数是1000,减去一是0111,与它自身与一下会变成0,以此来判断是否是2的幂
C++代码:
bool isPowerOf2(int n)
{
return (n & (n - 1)) == 0;
}
是否是4的幂思路:
如果是4的幂那么它一定是2的幂,所以先判断是否是2的幂,如果是,观察4的幂二进制位上的1的位置,4是100,16是10000,64是1000000。假设最右边的位是第0位,则4的幂的二进制的1都在偶数位上,找一个二进制数是......010101,跟4的幂与一下,结果一定不是0,如果结果是0则不是4的幂
C++代码:
bool isPowerOf4(int n)
{ //0x55555555的二进制就是......01010101
return (n & (n - 1)) == 0 && (n & 0x55555555) != 0;
}
题目3:给定两个有符号32位整数a和b,不能使用算术运算符,分别实现a和b的加、减、乘、除运算
[要求]
如果给定a、b执行加减乘除的运算结果就会导致数据的溢出,那么你实现的函数不必对此负责,除此之外请保证计算过程不发生溢出
加法
思路:两个数异或运算等于无进位相加,两个数与运算结果上的1等于这一位在加完需要进位,与运算左移一位就是进位信息,进位信息加上无进位相加的结果就等于原来的数相加的结果。在得到无进位相加结果和进位信息后,再把这两个数求无进位相加和进位信息,直到进位信息等于0,无进位相加的结果就是原来两个数相加的结果
int addition(int a, int b)
{
int sum = a;
while (b != 0)
{
sum = a ^ b;
b = (a & b) << 1;
a = sum;
}
return sum;
}
###减法
a-b = a+(-b)
int negative(int n)
{
return addition(~n, 1); //求n的相反数,任何数的相反数是它取反+1。
}
int subtraction(int a, int b) //做减法
{
return addition(a, negative(b));
}
###乘法
a乘以b,定义一个初始值为0的变量result用来累加结果,第一次ab移动位数是0,看b最右边的位上是不是1,如果是result就累加上a。然后b向右移1位,a向左移动一位,重复前面的步骤,直到b等于0。这是利用了手算乘法时的原理,演算时,在下面的数依次从个位、十位、百位等上的数乘以上面的数,每次乘完的结果都要比前一次的结果进一位,然后所有的结果相加。二进制中都是1,1乘以任何数还等于任何数,所以就可以看作是直接加上上面的数,每次加完进一位。
int multiplication(int a, int b)
{
int result = 0;
while (b != 0)
{
if ((b & 1) != 0)
{
result = addition(result, a);
}
a <<= 1;
b >>= 1;
}
return result;
}
###除法
除法的原理是这样的,假设a➗b,那么把b左移到刚好b不大于a,再移一位b就大于a了,然后记录这时左移的位数,最后除法结果这个位数上必是1,用a减去左移后的b得到a',再用a'去重复上面的操作,直到不移动a也小于b为止,找到所有位上的1。用10进制举例,假设222➗20,你先让20乘以10,就是左移一位,刚好不大于222,这时222有一个200,所以结果必有一个10,累加上,用222-200,得到22,20移动0位,22有一个20,结果必有一个1累加上得11,剩余22-20=2,小于22,所以结果就是11。
int divis(int a, int b)
{
int x = sign(a) ? a : negative(a); //如果a是负数,把a变成整数储存在x里
int y = sign(b) ? b : negative(b); //如果b是负数,把b变成整数储存在y里
int res = 0;
for (int i = 31; i > -1; i = subtraction(i, 1))
{
if ((x >> i) >= y) //用x去逼近y,而不是y去逼近x,这样可以避免溢出
{
res |= (1 << i);
x = subtraction(x, (y << i));
}
}
return sign(a) ^ sign(b) ? negative(res) : res; //如果a和b的符号相反,返回负数,相同则返回正数
}
int division(int a, int b) //主函数,考虑溢出问题
{
if (b == 0)
{
throw "Division by zero condition!"; //除数为0,抛出异常
}
if (a == INT_MIN && b == INT_MIN)
{
return 1;
}
else if (b == INT_MIN)
{
return 0;
}
else if (a == INT_MIN)
{
int res = divis(addition(a, 1), b);
return addition(res, divis(subtraction(a, multiplication(res, b)), b));
}
else
{
return divis(a, b);
}
}
浙公网安备 33010602011771号