首页 > 其他分享 >7、位运算,位图

7、位运算,位图

时间:2023-02-20 17:44:49浏览次数:45  
标签:return 运算 int res num 64 public

1、位运算

顾名思义,位运算符作用于位,是逐位进行操作。最常用的有:与 &、或 |、异或 ^。
对于基本的位运算,我们有一个常用的口诀:

  • 1.按位与 &:遇0则0
  • 2.按位或 |:遇1则1
  • 3.按位异或 ^ :相同为0,相异为1
  • 4.左移 <<: a << n,相当于 a * 2^n
  • 5.带符号右移 >>: a >> n,相当于 a / 2^n,用符号位补齐
  • 6.无符号右移 >>>: a >>> n,用0补齐

1.1、正负数表示

  • 正数的符号位为0
  • 负数的符号位为1

1.2、补码

负数 = 其绝对值取反加一,例如:-2
2: 0000 0010
~2:1111 1101
+1:1111 1110
利用补码,负数就没有了,从而就把“减法转换为加法运算”,利用这个特点,计算机中,仅需一个“加法器”,就够用了。

1.3、打印一个整数的 32 位信息

public class BitOperation {
    public static void print(int num) {
        for (int i = 31; i >= 0; i--) {
            System.out.print((num & (1 << i)) == 0 ? "0" : "1");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // 00000000000000000000000001000000
        print(64);
    }
}

2、位图的功能

使用二进制位来保存信息
以long[]数组为例,long 类型8个字节,64位
num % 64 相当于num & 63,但是&的效率 > %的
因为:7个二进制位可以表示0~63共64个数
所以:舍弃掉7位以上的高位,剩下的数即为 mod
num % 64,相当于num & 63

3、位图的实现

添加数据使用逻辑或 |,删除/查找数据使用逻辑与 &
例:第二位要保存数据,则原数据 | (1L << 2)
0 1 1 0 1 0 1 0
0 0 0 0 0 1 0 0 ->(1L << 2)
结果:0 1 1 0 1 1 1 0

点击查看代码
public class BitMap2 {

    public static class BitMap {
        private long[] bits;

        /**
         * 右移 >> 6 位相当于除以 2的6次方
         * + 64,是防止 max < 64的情况
         * 一个long类型,8个字节,64位
         *
         * @param max 最大整数
         */
        public BitMap(int max) {
            this.bits = new long[(max + 64) >> 6];
        }

        /**
         * num >> 64 -> num / 64 -> num存放在第几个整数里
         * num % 64 -> num & 63
         * 使用逻辑或 |,将数据写入
         * 0 0 0 0 0,例如找到数据要保存在第2位
         * 0 0 1 0 0,| 逻辑或,则将数据存入
         *
         * @param num 保存在位图中的数
         */
        public void add(int num) {
            bits[num >> 6] |= (1L << (num & 63));
        }

        public void delete(int num) {
            bits[num >> 6] &= ~(1L << (num & 63));
        }

        public boolean contains(int num) {
            return (bits[num >> 6] & (1L << (num & 63))) != 0;
        }
    }

    public static void main(String[] args) {
        System.out.println("测试开始");
        int max = 10000;
        BitMap bitMap = new BitMap(max);
        HashSet<Integer> set = new HashSet<>();
        int testTimes = 100000;
        for (int i = 0; i < testTimes; i++) {
            int num = (int) (Math.random() * max);
            double decide = Math.random();
            if (decide < 0.33) {
                bitMap.add(num);
                set.add(num);
            } else if (decide < 0.66) {
                bitMap.delete(num);
                set.remove(num);
            } else {
                if (bitMap.contains(num) != set.contains(num)) {
                    System.out.println("contains error");
                    break;
                }
            }
        }

        for (int num = 0; num < max; num++) {
            if (bitMap.contains(num) != set.contains(num)) {
                System.out.println("contains error");
                break;
            }
        }
        System.out.println("测试结束");
    }
}

3、使用位运算进行加减乘除

3.1、加法:进位和 + 无进位和

  • a,b无进位相加,相当于按位异或 a ^ b
  • a + b进位信息,相当于按位与之后左移一位(a & b) << 1
  • 重复上述步骤,直到进位为0时的 a' ^ b'即为 a + b

3.2、减法

  • a - b相当于,a加上,b的相反数 a - b -> a + (-b)
  • b的相反数为,b按位取反加一,~b + 1

3.3、乘法

  • a * b 使用二进制计算
  • 1.判断(b & 1)是否为 0,不为0则将结果加入
  • 2.a 左移一位 a << 1
  • 3.b 无符号右移一位 b >>> 1
  • 4.当b==0时,返回 res,即为 a * b

3.4、除法

a / b = c

  • a减去 b << n位后不大于a的数,此时商1
  • 不够减商 0
  • 重复上述步骤,直到 b << 0位即结果
  • b << 可能会越界,我们使用 a >>
    例如101100101除以111:

    测试链接:https://leetcode.com/problems/divide-two-integers

3.5、代码实现

点击查看代码
public class BitAddMinusMultiDiv {

    /**
     * 使用位运算实现,两个整数相加(支持负数)
     * 例:46 + 20
     * a:    0 1 0 1 1 1 0
     * b:    0 0 1 0 1 0 0
     * --
     * a':   0 1 1 1 0 1 0 -> 无进位相加,相当于异或 a ^ b
     * b':   0 0 0 1 0 0 0 -> 进位信息(0 0 0 0 1 0 0) (a & b) << 1,因为进位信息是在前一位,所以 & 之后左移一位
     * --
     * a'':  0 1 1 0 0 1 0
     * b'':  0 0 1 0 0 0 0
     * --
     * a''': 0 1 0 0 0 1 0
     * b''': 0 1 0 0 0 0 0
     * --
     * a'''':0 0 0 0 0 1 0
     * b'''':1 0 0 0 0 0 0
     * --
     * a''''':1 0 0 0 0 1 0
     * b''''':0 0 0 0 0 0 0
     * 进位信息为0时,结果 sum = a''''' = a'''' ^ b''''
     *
     * @param a 整数 a
     * @param b 整数 b
     * @return a 加 b
     */
    public static int add(int a, int b) {
        int sum = a;
        while (b != 0) {
            // 无进位相加,相当于异或 ^
            sum = a ^ b;

            // 进位信息 -> b ~ b' (进位信息)
            b = (a & b) << 1;

            // a -> a' 无进位相加信息
            a = sum;
        }
        return sum;
    }

    /**
     * 计算 n 的相反数
     *
     * @param n 整数
     * @return -n
     */
    public static int negNum(int n) {
        return add(~n, 1);
    }

    /**
     * 两个整数相减,a - b -> a + (-b)
     *
     * @return a - b
     */
    public static int minus(int a, int b) {
        return add(a, negNum(b));
    }

    /**
     * a * b 使用二进制计算
     * 1.判断(b & 1)是否为 0,不为0则将结果加入
     * 2.a 左移一位 a << 1
     * 3.b 无符号右移一位 b >>> 1
     * 4.当b==0时,返回 res,即为 a * b
     * a:     0 1 1 0
     * b:     0 1 0 1
     * -------------------
     * *      0 1 1 0
     * *    0 0 0 0
     * *  0 1 1 0
     * *0 0 0 0
     * -------------------
     * *0 0 1 1 1 1 0
     * <p>
     * 1--(b & 1)!= 0
     * res = 0110
     * a: 1 1 0 0
     * b: 0 0 1 0
     * <p>
     * 1--(b & 1)== 0
     * a: 1 1 0 0 0
     * b: 0 0 0 0 1
     * <p>
     * 1-- (b & 1)!= 0
     * res = 0110 + 11000 = 11110
     * a: 1 1 0 0 0 0
     * b: 0 0 0 0 0 0
     * b == 0 结束循环
     *
     * @return a * b
     */
    public static int multi(int a, int b) {
        int res = 0;
        while (b != 0) {
            if ((b & 1) != 0) {
                res = add(res, a);
            }
            a <<= 1;
            b >>>= 1;
        }
        // b == 0
        return res;
    }

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

    /**
     * a / b
     * 不能处理系统最小值,因为最小值的相反数还是最小值
     * 由于不支持负数,先将 ab 都转为正数相除,最后补符号
     * a / b = c
     * a / 0110 = 0101
     * ------------------------
     * b:     0 1 1 0
     * c:     0 1 0 1
     * -*------------------ b * c
     * *      0 1 1 0
     * *    0 0 0 0
     * *  0 1 1 0
     * a: 0 1 1 1 1 0
     * -----------------------
     * 则:a = b * c = b * 2^0 + b * 2^2
     * 即:c = a / b = 2^0 + 2^2  则为,0101
     * * 计算:
     * a:  011110
     * b:    0110
     * b左移到最接近 a 的位置,不大于a
     * ----------
     * a:  011110
     * b':  11000   b<<2
     * c: 0100  c的第2位为1
     * -------------
     * a': 000110   a - b'
     * b':   0110   b<<0
     * c: 0101 c的第0位为1
     * -------------
     * a': 000000   a' - b'
     * 当 a' 为0,结束,此时的c'即 a / b 的结果
     *
     * @return a / b
     */
    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;
        // x / y
        for (int i = 30; i >= 0; i = minus(i, 1)) {
            // 被除数 x 右移避免越界
            if ((x >> i) >= y) {
                res |= (1 << i);
                x = minus(x, y << i);
            }
        }
        // 符号不同为负
        return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
    }

    /**
     * div(int a, int b) 不能处理系统最小值
     * 解决系统最小转绝对值
     *
     * @return a / b
     */
    public static int divide(int a, int b) {
        // ab 均为最小值
        if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
            return 1;
        }
        // else b为最小值
        else if (b == Integer.MIN_VALUE) {
            return 0;
        }
        // else a为最小值
        else if (a == Integer.MIN_VALUE) {
            // b = -1
            if (b == negNum(1)) {
                return Integer.MAX_VALUE;
            }

            /*
             * a / b
             * (a + 1) / b = c
             * a - (c * b) = d 相差多少
             * d / b = e 差值除以 b
             * res = c + e 结果 = c + e差值
             *
             * 例如: -20 / 5
             * 19 / b = 3 c
             * 20 - 3 * b = 5 d
             * 5 / b = 1 e
             * res = c + e = 3 + 1 = 4
             */
            int ans = div(add(a, 1), b);
            return add(ans, div(minus(a, multi(ans, b)), b));
        }
        // b == 0,抛出 / by zero异常
        else if (b == 0) {
            return 1 / 0;
        }
        // else ab 都不是系统最小值
        else {
            return div(a, b);
        }
    }

    public static void main(String[] args) {
        int a = -19;
        int b = 5;
        int res = divide(a, b);
        System.out.println(res);
    }
}

标签:return,运算,int,res,num,64,public
From: https://www.cnblogs.com/kaibo-li/p/16911856.html

相关文章