标签:log 复杂度 str htm 代码 auto flag cell 方法
? 可以这样说,位运算是我们刚开始学计算机就会接触到的一种东西。那么位运算这么常见,我们是否可以使用它来做一些骚操作呢?
使用的运算符包括下面(java还有一个>>>无符号右移):
| 含义 | 运算符 | 例子 | 
|---|---|---|
| 左移(后面补0) | << | 0011 => 0110 | 
| 右移(正数前面补0,负数补1) | >> | 0110 => 0011 | 
| 按位或 | ︳ | 0011 ------- => 1011 1011 | 
| 按位与 | & | 0011 ------- => 1011 1011 | 
| 按位取反 | ~ | 0011 => 1100 | 
| 按位异或 (相同为0不同为1) | ^ | 0011 ------- => 1000 1011 | 
左移右移运算
右移相当于是除,左移相当于就是乘,左移一位乘以2,左移二位乘以4,依此类推.无论正数、负数,它们的右移、左移、无符号右移32位都是其本身,
加法有两个操作:求和与进位
求和: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的二进制相乘。

上面我们可以发现,相乘有点类似与&,只不过它会进位而已。那么我们的问题就可以简单化了。相与,然后进位即可,然后再相加。换个方向想: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,今天的位运算四则运算就到这里了,在这里我都没有考虑到数据溢出的情况,因为重点不在这里,下次的博客我将会说一下位运算在算法中的一些骚操作。
标签:log 复杂度 str htm 代码 auto flag cell 方法
原文地址:https://www.cnblogs.com/xiaohuiduan/p/10981210.html