22.1.30 位运算

22.1.30 位运算

1.资源限制类题目的技巧:

1))哈希函数可以把数据按照种类均匀分流;

2)布隆过滤器用于集合的建立与查询,并可以节省大量空间;

3)一致性哈希解决数据服务器的负载管理问题;

4)利用并查集结构做岛问题的并行计算;

5)位图解决某一范围上数字的出现情况,并可以节省大量空间;

6)利用分段统计思想、并进一步节省大量空间;

7)利用堆、外排序来做多个处理单元的结果合并;

2.位运算

1)判断一个32位正数是否为2的幂或者4的幂;
  • 2的幂:某一个数为2的幂的特点是这个数的二进制表示中只有一个1;

    • x&(x-1) == 0;
      //成立则表示这个数的二进制表示中只有一个1;
  • 4的幂:4的幂一定是2的幂,且4的幂的二进制表示上的1一定分布在0,2,4,6,8....... 位上;

    • x&(x-1) == 0;
      //成立则表示这个数的二进制表示中只有一个1;
      x&(0x55555555) != 0;
      //0x55555555表示的二进制数为......010101;
2)返回a,b中较大的那个32位整数:
  • 要求:不做任何比较

  • 要点:“+”可以将互斥条件的if else写成数学形式;

  • code:

  •     public static void main(String[] args)
    {
    System.out.println(getMax(-6, 5));
    }

    public static int sign(int x)
    {
    return flip((x >> 31) & 1);
    }
    //右移31位取出x的符号位;
    //0正1负;
    //经历flip函数后0表示负数,一表示正数;

    public static int flip(int n)
    {
    return n ^ 1;
    }
    //保证n的输入只能是1或者0;
    //输入为1,输出为0;
    //输入为0,输出为1;

    public static int getMax(int a, int b)
    {
    int c = a - b;
    int sa = sign(a);
    int sb = sign(b);
    int sc = sign(c);
    //取得a,b,c的符号位
    int difSab = sa ^ sb;
    //判断a,b的符号情况
    int sameSab = flip(difSab);
    int returnA = difSab * sa + sameSab * sc;
    //返回a有两种情况(+可以理解为或,*理解为且);
    //a,b的符号位不同,a为正时返回a;
    //a,b的符号位相同,c的符号位为正是返回a;
    int returnB = flip(returnA);
    //不返回a就返回b
    return a * returnA + b * returnB;
    //“+”可以将互斥条件的if   else写成数学形式;
    }
3)位运算实现四则运算:
  • 要求:给定两个有符号位的32位整数a,b,不使用简单运算符,实现加减乘除;

  • 加法(假设给定a=13:01101和b=7:00111,实现13和7的相加):

    • 过程模拟:

      • 先将两个数进行异或(无进位相加),得到01010;

      • 再将两个数进行&运算并将结果左移一位得到01010;

      • 将第一步和第二步得到的数分别更新为a,b,重复前两步直到无进位信息,即第二部的运算结果全为0,此时第一步的结果就是a+b的结果;

    • code:

      •     public static void main(String[] args)
        {
        System.out.println(getSum(13,7));
        }

        public static int getSum(int a,int b)
        {
        int sum = 0;
        while(b!=0)
        {
        sum = a^b;
        b = (a&b)<<1;
        a = sum;
        }
        return sum;
        }
  • 减法(假设给定a=13:01101和b=7:00111,实现13和7的相减)

    • 思路:13-7相当于13+(-7),求得7的相反数,再利用加法进行相加即可

    • code:

      •     public static void main(String[] args)
        {
        System.out.println(getSum(13,negB(7)));
        }

        public static int getSum(int a,int b)
        {
        int sum = 0;
        while(b!=0)
        {
        sum = a^b;
        b = (a&b)<<1;
        a = sum;
        }
        return sum;
        }

        public static int negB(int b)
        {
        return getSum(~b,1);
        }
        //得到B的相反数,~:补码的符号
  • 乘法:

    • 思路:二进制乘法类似与我们使用的竖式乘法,将第二个二进制数的每一位都与第一个数的每一位相乘展开,最后求进位和;

    • 过程模拟:(给定数a =26= 011010,b =22= 010110)

      •                     0 1 1 0 1 0
                             0 1 0 1 1 0
           * ------------------------------------------            
                             0 0 0 0 0 0
                           0 1 1 0 1 0
                         0 1 1 0 1 0
                       0 0 0 0 0 0
                     0 1 1 0 1 0
                   0 0 0 0 0 0
             ------------------------------------------
                   0 1 0 0 0 1 1 1 1 0 0
             结果所得到的二进制数转换为十进制为572
      • code:

        •     public static void main(String[] args)
          {
          System.out.println(multi(13,7));
          }

          public static int multi(int a,int b)
          {
          int res = 0;
          while(b!=0)
          {
          if((b&1)!=0)
          {
          res = getSum(res,a);
          a<<=1;
          b>>>=1;
          }
          }
          return res;
          }

          public static int getSum(int a,int b)
          {
          int sum = 0;
          while(b!=0)
          {
          sum = a^b;
          b = (a&b)<<1;
          a = sum;
          }
          return sum;
          }
    • 除法:

      • 过程模拟:(假设给定0111100/00101)

        • 除法相当于乘法的逆过程;

        • 将0000101尽量左移到大小最接近0111100的程度,但是不能超过0111100.则第一次移动可以知道是左移3位得到0101000,根据上述乘法的过程模拟能知道结果的2^3位上有1;

        • 更新被除数的值:将0111100-0101000=0010100设置成被除数;

        • 继续重复上述过程:把0000101左移两位得到0010100,同理2^2位上有1;

        • 此时0010100-0010100=0,过程结束,结果就为01100(二的二、三次方位上有一,其余都是零);

      • 上述过程是我们脑海中的乘法逆过程的原理,在实际过程中左移可能导致溢出或者改变符号位,因此在实际过程中通常采用右移被除数达到接近除数的过程。

      • code:


        • public static boolean isNeg(int n) {
          return n < 0;
          }

          public static int negNum(int n) {
          return add(~n, 1);
          }

          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;
          }

          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) {
          int res = div(add(a, 1), b);
          return add(res, div(minus(a, multi(res, b)), b));
          } else {
          return div(a, b);
          }
          }
        •  

 

posted @ 2022-01-30 23:37  张满月。  阅读(263)  评论(0编辑  收藏  举报