算法学习(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的幂思路:

  1. 一个数如果是2的幂,则它的二进制数只能有一个1,拿到最右侧的1,然后跟原来的数比较,相等则是2的幂
  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);
}
}

posted @ 2022-08-03 22:06  小肉包i  阅读(16)  评论(0)    收藏  举报