• 位运算


    &:按位与

    二进制位均为1,则为1。

    举例:

    1001 & 1000 = 1000

    |:按位或

    二进制位有一个为1,则为1。

    举例:

    1001 & 1000 = 1001

    ^:按位异或

    二进制位不同为1。

    举例:

    1001 ^ 1000 = 0001

    ~:取反

    按位取反,为一元运算符。

    举例:

    ~1001 = 0110

    <<:左移运算符

    一个数的二进制位全部左移N位,右位补0。

    举例:

    1001 << 1 = 10010

    (a<<b,实际上为a * 2^b)

    >>:右移运算符

    一个数的二进制位全部右移N位,左位补0还是1取决于计算机系统。

    对于无符号数,左位补0。

    对于有符号数,有机器将对左位用符号位填补(即“算术移位”),有机器则对左位用0填补(即“逻辑移位”)。即:如果原来符号位为0(正数),则左位补0。如果符号位原来为1(负数),则左位补0还是1,要取决于所用的计算机系统。

    当a是正整数时,a>>b == a / (2^b)

    当a是负整数时,a>>b == a / (2^b) 上取整。

     

    位运算技巧

    技巧一:N&(N-1)可以消去N的最后一位的1

    应用:

    用O(1)时间检测整数n是否是2的幂次(N的二进制表示中只有一个1,且N>0)

    二进制表示中有多少个1(循环消1即可)

    将整数A转换为B,需要改变多少个bit位(A和B有多少个BIT位不相同,A异或B之后这个数中1的个数)

     

    技巧二:子集枚举

    对于大小为N的数组,判断N个二进制位中每一位是0还是1,1表示包括该位置数,0表示不包括,可生成2^N个子集

     

    技巧三:a^b^b=a

    应用:

    只有一个数出现一次,剩下都出现两次,找出出现一次的(所有的数异或可以得到唯一的数)

    只有一个数出现一次,剩下都出现三次,找出出现一次的(如果只出现一次的数在该二进制位为1,那么这个二进制位在全部数字中出现次数无法被3整除)

    只有两个数出现一次,剩下都出现两次,找出出现一次的(所有的元素异或的结果就是res=x^y。并且res!=0,那么我们可以找出res二进制表示中的某一位是1,那么这一位1对于这两个数x,y只有一个数的该位置是1。对于原来的数组,我们可以根据这个位置是不是1就可以将数组分成两个部分。求出x,y其中一个,就能求出两个了。)

    1 #include<stdio.h>
     2 
     3 void findNum(int *a,int n)
     4 {
     5     int ans=0;
     6     int pos=0;
     7     int x=0,y=0;
     8     for(int i=0;i<n;i++)
     9         ans^=a[i];
    10     int tmp=ans;
    11     while((tmp&1)==0){
    12     //终止条件是二进制tmp最低位是1
    13             pos++;
    14             tmp>>=1;
    15     }
    16     for(int i=0;i<n;i++){
    17         if((a[i]>>pos)&1){//取出第pos位的值
    18             x^=a[i];
    19         }
    20     }
    21     y=x^ans;
    22     if(x>y) swap(x,y);//从大到小输出x,y
    23     printf("%d %d
    ",x,y);
    24 }
    25 int main()
    26 {
    27     int a[8]={1,2,2,3,4,4,5,3};
    28     findNum(a,8);
    29 }
    

    技巧总结

    功能 示例 位运算公式
    去掉最后一位 101101  ->  10110 x >> 1
    最后加一个0 101101  ->  1011010 x << 1
    最后加一个1 101101  ->  1011011 (x << 1) + 1
    最后一位变成0 101101  ->  101100 (x | 1) - 1
    最后一位变成1 101100  ->  101101 x | 1
    最后一位取反 101101  ->  101100 x ^ 1
    右边第k位变成0 101101  ->  101001     k=3 x | (1 << (k-1))
    右边第k位变成1 101001  ->  101101     k=3 x &~ (1 << (k-1))
    右边第k位取反 101101  ->  101001     k=3 x ^ (1 << (k-1))
    取最后三位 101101  ->  101 x & 0b111
    取最后k位 101101  ->  1101        k=4 x & ((1<<k) - 1)
    取右边第k位 101101  ->  1             k=4 x>>(k - 1) & 1
    右边k位变成1 101101  ->  101111    k=4 x | ((1<<k) - 1)
    右边k位取反 101101  ->  100010 x ^ ((1<<k) - 1)
    右边连续的1变成0 100101111  ->  100100000 x & (x + 1)
    右边连续的0变成1 101101000  ->  101101111 x | (x - 1)
    右边第一个0变成1 101101  ->  101111 x | (x + 1)
    取右边连续的1 101111  ->  1111 (x ^ (x + 1)) >> 1
    去掉右边第一个1的左部分 101101000  ->  1000 x & (-x)

    负数的二进制表示

    计算机中:负数以原码的补码形式表达

    为何101101000 -> 1000   =====    x & (-x) ?

     

    296:00000000 00000000 00000001 00101000

    296的原码:00000000 00000000 00000001 00101000(正数,按照绝对值大小转换成的二进制数)

    -296的原码:10000000 00000000 00000001 00101000(负数按照绝对值大小转换成的二进制数,然后最高位补1)

     

    反码:

    正数的反码与原码相同。296的反码:00000000 00000000 00000001 00101000

    负数的反码为对原码除符号位外取反。-296的反码:11111111 11111111 11111110 11010111

     

    补码:

    正数的补码与原码相同。296的补码:00000000 00000000 00000001 00101000

    负数的补码为对原码除符号位外各位取反,然后在最后一位加1。-296的补码:11111111 11111111 11111110 11011000

     

    那么-296在计算机中表示为:11111111 11111111 11111110 11011000

     

    因此:296 & (-296) = 8      100101000 & 011011000   =(0b1000)

     

     

     

  • 相关阅读:
    性能测试的一些大实话:会linux命令以及其它工具,到底能不能做性能分析调优?
    使用Docker方式部署Gitlab,Gitlab-Runner并使用Gitlab提供的CI/CD功能自动化构建SpringBoot项目
    Docker安装Gitlab
    Docker部署ELK
    Dockerfile中ADD命令详细解读
    使用Gitlab CI/CD功能在本地部署 Spring Boot 项目
    SSH 克隆跟HTTP 克隆地址的区别
    Docker安装Gitlab-runner
    Docker方式安装Jenkins并且插件更改国内源
    使用docker-compose部署SonarQube
  • 原文地址:https://www.cnblogs.com/jpzhu/p/14649372.html
Copyright © 2020-2023  润新知