• 位运算的一些有用的操作


    位运算的骚操作(一)之四则运算

    ​ 可以这样说,位运算是我们刚开始学计算机就会接触到的一种东西。那么位运算这么常见,我们是否可以使用它来做一些骚操作呢?

    使用的运算符包括下面(java还有一个>>>无符号右移):

    含义运算符例子
    左移(后面补0) << 0011 => 0110
    右移(正数前面补0,负数补1) >> 0110 => 0011
    按位或 0011 ------- => 1011 1011
    按位与 & 0011 ------- => 1011 1011
    按位取反 ~ 0011 => 1100
    按位异或 (相同为0不同为1) ^ 0011 ------- => 1000 1011

    左移右移运算
    右移相当于是除,左移相当于就是乘,左移一位乘以2,左移二位乘以4,依此类推.无论正数、负数,它们的右移、左移、无符号右移32位都是其本身,

    位运算与四则运算(这里都是考虑整数【Java版本】)

    加法

    加法有两个操作:求和进位

    求和:1+1=0 1+0=1 0+0=0

    异或:1^1=0 1^0=1 0^0=0

    进位:1+1=1 1+0=0 0+0=0

    位与:1&1=1 1&0=0 0&0=0

    那么这时候,我们使用位运算实现加法肯定是使用异或位与

    • 递归版本

      // 使用递归实现
      int add(int a,int b){
          // 假如进位为0
          if(b ==0){
              return a;
          }
          // 获得求和位
          int s = a^b;
          // 获得进位,然后需要向做移动一位表示进位
          int c = ((a&b)<<1);
          return add(s,c);
      }
    • 非递归版本

      非递归版本和递归版本没什么不一样的

       int add2(int a,int b){
      
          int s;
          int c;
          while(b != 0){
              // 获得求和位
              s = (a^b);
              // 获得进位,然后需要向做移动一位表示进位
              c = ((a&b)<<1);
              a = s;
              b = c;
          }
          return a;
      }

    减法

    减法,emm,简单点来说,就是使用加法来做的,只是说加了一个负数而已

    首先我们得先将被减数取反(a取反就是~a+1

    int adverse(int a){
        return add(~a,1);
    }

    减法的完全代码

    // 取得相反数
    int adverse(int a){
        return add(~a,1);
    }
    // 减法函数 其中add就是前面的加法函数
    int subtract(int a,int b){
        return add(a,adverse(b));
    }

    乘法

    说乘法的前面我们先考虑下符号问题。

    首先呢,我们得知道一个数是正数还是负数

    // 负数返回-1,正数返回0
    int getsign(int i){
        return (i >> 31);
    }

    如果为负数,则进行取反

    // 如果为负数,则进行取反
    int toPositive(int a) {
        if (a >> 31 == -1)
            // 进行取反
            return add(~a, 1);
        else
            return a;
    }
    • 解决方法一:

      乘法,我们小时候学习乘法,老师告诉我们,乘法就是累加,例如6*7就是7个6相加而已,so,我们肯定可以使用加法来实现乘法。时间复杂度O(n)。

    int multiply(int a, int b){
        Boolean flag = true;
        // 如果相乘为正数,flag为false
        if (getsign(a) == getsign(b))
            flag = false;
        // 将a取正数
        a = toPositive(a);
        b = toPositive(b);
        int re = 0;
    
        while (b!=0) {
            // 相加
            re = add(re, a);
            // b进行次数减一
            b = subtract(b, 1);
        }
        // 假如结果是负数,则进行取反
        if (flag)
            re = adverse(re);
        return re;
    }
    • 解决方法二

      首先先看一张图吧,这张图是我用笔记本的触摸板画的,难看了一点(en,我自己都受不了的难看),上面画的是6*2的二进制相乘。

       

      1
      1

      上面我们可以发现,相乘有点类似与&,只不过它会进位而已。那么我们的问题就可以简单化了。相与,然后进位即可,然后再相加。换个方向想:a向左移动,b向右移动,如果b的最后一位为1,则可以将a加上。(时间复杂度O(logn))

      int multiply2(int a, int b) {
          Boolean flag = true;
          // 如果相乘为正数,flag为false
          if (getsign(a) == getsign(b))
              flag = false;
          // 将a取正数
          a = toPositive(a);
          b = toPositive(b);
          int re = 0;
          while (b!=0) {
              // 假如b的最后一位为1
              if((b&1) == 1){
                  // 相加
                  re = add(re, a);
              }  
              b = (b>>1);
              a = (a<<1);
          }
          // 假如结果是负数
          if (flag)
              re = adverse(re);
          return re;
      }

    除法

    除法和乘法类似。

    • 解决方法一

      除法解决方法一和乘法的解决方法一类似,从被除数上面减去除数,直到不够减了,才罢休。时间复杂度O(n)。

    • 解决方法二
      解决方法二的思路是这样的:从b*(2**i)开始减(2**i代表2的i次方),然后一直减到b*(2**0)。这样的解决方法的时间复杂度是O(logn)。

      // a/b
      int divide(int a,int b){
         Boolean flag = true;
         // 如果相除为正数,flag为false
         if (getsign(a) == getsign(b))
             flag = false;
         // 将a取正数
         a = toPositive(a);
         b = toPositive(b);
         int re = 0;
         int i = 31;
         while(i>=0){
             // 如果够减
             // 不用(b<<i)<a是为了防止溢出
             if((a>>i)>=b){
                 // re代表结果
                 re = add(re, 1<<i);
                 a = subtract(a, (b<<i));
             }
             // i减一
             i = subtract(i, 1);
         }
         // 假如结果是负数
         if (flag)
             re = adverse(re);
         return re;
      }

    ok,今天的位运算四则运算就到这里了,在这里我都没有考虑到数据溢出的情况,因为重点不在这里,下次的博客我将会说一下位运算在算法中的一些骚操作。

  • 相关阅读:
    VMware虚拟机找不到USB设备该怎么办?
    关于Ubuntu锁屏后,无法输入密码
    设计模式-第一篇之单例模式
    Java-多线程第一篇多线程相关认识(1)
    Quartz-第二篇 使用quartz框架定时推送邮件
    Quartz-第一篇 认识Quartz
    Web Service-第一篇什么是Web Service
    Linux-第二篇常用命令
    Linux-第一篇linux基本认识
    Oracle-常见的错误
  • 原文地址:https://www.cnblogs.com/xiaohuiduan/p/10981210.html
Copyright © 2020-2023  润新知