• 位运算的题目小结


    位运算的小技巧

    【n & 1 == 0 ? 偶数:奇数】可以用来判断是奇数还是偶数

    左移运算a<<1可以作为乘2使用

    右移运算a>>1可以作为除以2使用

    获得第 i 位【(a>>i)&1】或者【 a&(1<<i)】

    将第 i 位设为1【a=a|(1<<i)】

    将第 i 位设为0【a=a&(~(1<<i))】

    将第 i 位取反 【a=a^(1<<i)】

    当b是2的幂次的时候, a & (b-1)  == a % b

    判断x是否是2的幂【x & (x-1) == 0 ? 是:否】 【x & (-x) == x ? 是:否】

    获取最右侧的1,二进制中最右侧的1的位置为 1,且其它位全部为 0。【x&(-x)】

    去除最右侧的1,将最右边的 1 设置为 0:【x & (x - 1)】

    位运算的基础

    位操作都是对补码进行操作
    正数的补码是自己
    负数的补码是源码取反+1
     
    已知补码求反码:补码-1后取反,取反过程中符号位不变
    补码:源码取反+1,取反过程中符号位不变
     
    两次异或之后还是原值
    a^b^b = a 可以应用在加密上 b是密钥
     
    异或:实现两个变量值的交换
    a = a ^ b
    b = a ^ b 【a^b^b = a】
    a = b ^ a 【a^b^a = b】
    异或是满足交换律的
     
    <<左移 操作数 * 2
    >>右移 操作数 / 2
    >>>无符号右移 操作数/2
    区别:右移操作后 左边有位置空出来。 空位置用操作数的符号位置的数补全
    无符号右移 左边空出来的位置用0补全

    不使用比较判断找出较大值

     1     //参数n不是0 就是1
     2     // 1>0 0>1 实现1变0,0变1
     3     public static int flip(int n){
     4         return n ^ 1;
     5     }
     6     //n是负数 返回0
     7     //n是非负数 返回1
     8     public static int sign(int n){
     9         return flip((n >> 31) & 1);
    10         //n>>31是把符号位移到最后,
    11         // 如果是负数 ******1&00000001 = 1 -> flip -> 0
    12         // 如果是正数 ******0&00000001 = 0 -> flip -> 1
    13     }
    14     
    15     public static int getMax(int a, int b){
    16         int c = a-b;
    17         int signA = sign(c);//如果a>=b signC = 1; a<b signC = 0
    18         int signB = flip(signA);//signA反转
    19         return signA * a + signB * b;
    20     }

    上述代码存在问题:a-b可能会溢出

     1     public static int getMax2(int a, int b){
     2         int sa = sign(a);
     3         int sb = sign(b);
     4         int sc = sign(a-b);
     5         int difSab = sa ^ sb; //ab符号是否一样  一样 0 不一样 1
     6         int sameSab = flip(difSab);// 一样 1 不一样 0
     7         //如果a和b的符号相同 肯定不会溢出
     8         
     9         //在a和b符号相同的情况下 a-b>=0; 返回A【sameSab * sc】
    10         //在a和b符号不同的情况下可能溢出,但如果A大于0,可直接返回【difSab * sa】
    11         int returnA = sameSab * sc + difSab * sa;
    12         //A B互斥 直接反转
    13         int returnB = flip(returnA);
    14         return a * returnA + b * returnB;
    15         
    16     }

    上述代码解决了溢出问题。return的部分保证returnA和returnB互斥,只会返回a或b

    判断32位正数是不是2的幂,4的幂

    2的幂:2进制状态中只有1个1【充要条件】

    获取最右侧的1:x&(-x)

    【二进制中最右边的 1,且其它位全部为 0。】

    x & (-x) == x 那么x就是2的幂

    去除最右侧的1:x & (x - 1)

    【最右边的 1 设置为 0】

    (x - 1)代表了将 x 最右边的 1 设置为 0,并且将较低位设置为 1。

    如果 x & (x - 1) == 0 那么 x就是2的幂【因为它只有1位是1】

    4的幂:4的幂一定是2的幂,所以4的幂也只有1个1【必要不充分条件】

    4的幂的1一定在 1位、3位、5位、7位....上(奇数位)

    32位 0x55555555 = 0101010101....0101(奇数位上全是1)

    所以只要x & 010101.....010101 != 0  则是4的幂

    1     public static boolean isTowPower(int x){
    2         return (x & -x) == x;//取出1
    3         //return (x & (x-1)) == 0;//去掉1
    4     }
    5     public static boolean isFourPower(int x){
    6         return isTowPower(x) && (x & 0x55555555)!=0;
    7     }

    不使用算数运算符实现a和b的加减乘除

    加法

    前提:a和b运算后的结果一定不会溢出。

    异或:无进位相加(不包含进位信息的加法结果)

    进位信息:&之后左移1位

    12 + 6

    01100

    00110

    ^--------

    01010

    &-------

    00100

    <<------

    01000

    异或结果+进位信息 直到不再产生进位信息

    01010

    01000

    ^-------

    00010

    &------

    01000

    <<-----

    10000

    异或结果+进位信息 直到不再产生进位信息

    00010

    10000

    ^-------

    10010

    &------

    00000

    得到结果:10010 = 2+16 = 18

    1     public static int add(int a, int b){
    2         int sum = a;
    3         while(b!=0){
    4             sum = a ^ b;//无进位相加
    5             b = (a & b) << 1;//进位信息
    6             a = sum;//无进位相加+进位信息 直到 进位信息==0
    7         }
    8         return sum;
    9     }

    减法

    a - b = a + (-b)

    相反数:一个数取反+1,就是这个数的相反数(求负数补码的方法)

        public static int sub(int a,int b){
            return add(a,add(~b,1));
        }

    乘法

    被乘数 X 乘数 = 

    对于乘数的每一位,如果是 0 :00000000左移1位

    如果是1:被乘数左移1位(在上一次的基础上左移)

    +--------------------------------------------------------------------

    得到结果

    00001000

    00000110

    ------

    00010000

    00000011

    ------

    add (00010000+0=00010000)

    00100000

    00000001

    add (00100000+00010000=00110000)

    ------

    01000000

    00000000

    返回00110000=32+16 = 48 √

        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;//无符号右移用0补位
            }
            return res;
        }

    除法

    a / b

    将b左移到可以被a减去的最大值(即:b左移后 b' <= a)

    a = a - b' 直到 a - b' = 0

    这个过程只适用于a >= 0 && b >= 0的情况

    所以需要把a和b都转成正数,计算完后再判断res的真实符号

     1     public static int div(int a, int b){
     2         int x = a > 0 ? a:add(~a,1);
     3         int y = b > 0 ? b:add(~b,1);
     4         int res = 0;
     5         for(int i = 31; i > -1; i = sub(i,1)){
     6             if((x >> i) >= y){
     7                 res |= (1<<i);
     8                 x = sub(x, y << i);
     9             }
    10         }
    11         return isNeg(a) ^ isNeg(b) ? add(~res,1):res;
    12     }
  • 相关阅读:
    Pandas也能轻松绘图,简单而又漂亮
    笔试题: 二叉排序数左移k个
    补题next_permutation
    从HTTP到HTTPS
    HTTP首部字段详解
    HTTP请求方法及响应状态码详解
    HTTP报文格式详解
    TCP/IP网络基础
    Netty学习笔记
    ZooKeeper学习笔记
  • 原文地址:https://www.cnblogs.com/xdcat/p/12984786.html
Copyright © 2020-2023  润新知