[算法入门笔记] 17. 位运算

找出两个数中较大的数

给定两个32位整数ab,返回ab较大的数,要求不做任何比较

/**
 * 如果n为1返回0,如果为0,返回1
 * @param n
 * @return
 */
public static int flip(int n) {
    return n ^ 1;
}

/**
 * 返回整数n的符号,正数和0返回1,负数返回0
 * @param n
 * @return
 */
public static int sign(int n) {
    return flip((n >> 31) & 1);
}

/**
 * 返回两个数中最大的数
 * @param a
 * @param b
 * @return
 */
public static int getMax(int a, int b) {
    int c = a - b;
    int sa = sign(a);
    int sb = sign(b);
    int sc = sign(c);
    //difSab=1 sameSab=0 表示两个数符号不同
    //difSab=0 sameSab=1 表示两个数符号相同
    int difSab = sa ^ sb;
    int sameSab = flip(difSab);

    int returnA = difSab * sa + sameSab * sc;
    int returnB = flip(returnA);
    return a * returnA + b * returnB;
}
  • flip()函数表示取反
  • sign()函数表示取整数的符号
  • 如果单纯靠判断(a-b)符号会有溢出风险

因此,

如果a符号和b符号不同(difSab=1 sameSab=0 表示两个数符号不同)

  • 如果a为0或正,那么b为负(sa=1,sb=0),返回a
  • 如果a为负,那么b为0或正(sa=0,sb=1),返回b

如果a符号与b符号相同(difSab=0 sameSab=1 表示两个数符号相同),a-b绝对值不会溢出

  • 如果a-b为0或正(sc=1),返回a
  • 如果a-b为负(sc=0),返回b

加减乘除运算

给定两个32位整数ab,可正,可负,可0,不使用算术运算,实现ab的加减乘除运算,但给定的ab执行加减乘除的某些结果本来就会导致数据溢出,可以不必理会

加法运算
  • 在不考虑进位的情况下,a^b就是正确结果

在这里插入图片描述

  • 只算进位的情况下(只考虑a和b相加的过程中进位产生的值是什么),结果就是(a&b)<<1

因为第i位上只有1与1相加才产生i-1进位

在这里插入图片描述

  • 完全不考虑进位的相加值与只考虑进位相加的值再相加,就是最终结果,一直重复,直到进位产生的值完全消失,说明加完

在这里插入图片描述

/**
 * 位运算实现加法
 * @param a
 * @param b
 * @return
 */
public static int add(int a, int b) {
    int sum = a;
    while (b != 0) {
        //无进位相加
        sum = a ^ b;
        //进位结果
        b = (a & b) << 1;
        a = sum;
    }
    return sum;
}
减法运算

位运算实现减法,只要实现a+(-b)即可,得到一个相反数就是数的二进制取反+1

/**
 * 求相反数
 * @param n
 * @return
 */
public static int negNum(int n) {
    return add(~n, 1);
}

/**
 * 位运算求减法
 * @param a
 * @param b
 * @return
 */
public static int minus(int a, int b) {
    return add(a, negNum(b));
}
乘法运算

在这里插入图片描述

位运算实现乘法。

a x b = a x 2 0 x b 0 + a x 2 1 x b 1 + . . . + a x 2 i x b i + . . . + a x 2 31 x b 31 axb=ax2^0 xb_0 + ax2^1 xb_1 +...+ax2^ixb_i + ... + ax2^{31} xb_{31} axb=ax20xb0+ax21xb1+...+ax2ixbi+...+ax231xb31,其中bi是0或1代表整数b的二进制数表达中第i位的值

/**
 * 位运算求乘法
 * @param a
 * @param b
 * @return
 */
public static int multi(int a, int b) {
    int res = 0;
    while (b != 0) {
        if ((b & 1) != 0) {
            res = add(res, a);
        }
        //a左移1位
        a <<= 1;
        //b右移1位
        b >>>= 1;
    }
    return res;
}
除法运算

只适合a、b不是负数先找到a能包含的最大部分,然后让a减去这个最大部分,再让剩下的a找到次大部分,并依次找下去,如果a和b有一个为负数或者都为负数时,可以先把a和b转正数,计算完成后看res的真实符号

在这里插入图片描述

a、b都不为负数,假设a=286,b=22,res=0

b左移31位、30位、…、4位时得到的结果大于a,说明a包含不下bx232bx2^4^任何一个,所以res4res31这些位置都是0,当b左移3位时结果位010110000,此时a≥b,说明a可以包含一个bx23,即res3=1,

剩下的a,即a-bx23

在这里插入图片描述

b<<2为001011000,此时a≥b,说明剩下a可以包含一个bx22,即res2=1,

剩下的a,即a-bx23-bx22

在这里插入图片描述

b<<1后大于a,说明剩下的a不能包含bx21

b<<0后a==b,说明剩下的a能包含一个bx20,即res0=1

当剩下的a再减去一个b后,结果为0,说明a被b除净,结果就是此时的res,即000001101=13

上述只能适用于a和b都不是负数,a和b两个都是负数或者有一个负数可以先转成正数,再求解

/**
 * 判断是否为负数
 * @param n
 * @return
 */
public static boolean isNeg(int n) {
    return n < 0;
}

/**
 * 无法处理负数最大值的情况
 * @param a
 * @param b
 * @return
 */
public static int div(int a, int b) {
    int x = isNeg(a) ? negNum(a) : a;
    int y = isNeg(b) ? negNum(b) : b;
    int res = 0;
    for (int i = 31; i > -1; i = minus(i, 1)) {
        if ((x >> i) >= y) {
            res |= (1 << i);
            x = minus(x, y << i);
        }
    }
    return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}

最关键一步

32位整数最小值为-2147483648,最大值为2147483647,最小值的绝对值比最大值的绝对值大1

所以,a或b等于最小值是转换不成相应正数的

  • 如果a和b都不为最小值,返回div(a,b)
  • 如果a和b都为最小值,a/b==1
  • 如果a不为最小值,b为最小值,a/b=0
  • 如果a为最小值,b不为最小值,处理如下

假设整数的最大值为9,最小值为-10

  • 当a和b∈[0,9]
  • 当a和b∈[-9,9]
  • 当a和b都为-10
  • 当a∈[-9,9],b=-10
  • 当a=-10,b∈[-9,9]
  1. 假设a=-10,b=5
  2. 计算(a+1)/b,记为c -9 / 5
  3. 计算c*b -1 * 5 = -5
  4. 计算a-(c*b),-10-(-5)=-5
  5. 计算(a-(c*b))/b,记为rest,-5/5=-1
  6. 返回c+rest的结果
/**
 * 位运算求除法
 * @param a
 * @param b
 * @return
 */
public static int divide(int a, int b) {
    if (b == 0) {
        throw new RuntimeException("divisor is 0");
    }
    if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
        return 1;
    } else if (b == Integer.MIN_VALUE) {
        return 0;
    } else if (a == Integer.MIN_VALUE) {
        //res=(a+1)/b
        int res = div(add(a, 1), b);
        //res+(a-res*b)/b
        return add(res, div(minus(a, multi(res, b)), b));
    } else {
        return div(a, b);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyan Chau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值