zoukankan      html  css  js  c++  java
  • 强大的工具--位运算

    近日来在看书的过程当中被这样的一句话 假设hi和low是两个整数,它们的值介于0到15之间,如果r是一个8位整数,且r的低四位与low各位上的数一致,而r的高4位与hi各位上的数一致,很自然会想到要这样写:

    r = hi << (4 + low);   整的迷惑,说实话,位运算也学过,但使用最多的也就是在交换数值当中使用异或运算,而当我明白书中这句话里表达式的含义时,深深被其中的精妙所折服,于是在网上搜集了关于位运算的一些使用技巧,记录以下,以供后日参考(其中借鉴了csdn和百度上的一些问答,因为数量繁杂,记得不是太全,向这些博主表示衷心的感谢!):

    首先还是从最基础的说起,写程序位运算是必要的吗,以我的理解并不是,但是位运算正由于其本身能够直接操作底层二进制数的特性,能提高近百分之60左右的运算效率,也是值得去学习和深入研究的,在学校的学习中位运算,课堂的知识讲解的比较少,把c/c++中 位运算 的基础知识先罗列一下

    运算符 含义 功能
    & 按位与 如果两个相应的二进制位都为1,则该位的结果值为1;否则为0。
    | 按位或 两个相应的二进制位中只要有一个为1,该位的结果值为1。
    按位异或 若参加运算的两个二进制位同号则结果为0(假)异号则结果为1(真)
    取反 ~是一个单目(元)运算符,用来对一个二进制数按位取反,即将0变1,将1变0。
    << 左移 左移运算符是用来将一个数的各二进制位全部左移N位,右补0。
    >> 右移 表示将a的各二进制位右移N位,移到右端的低位被舍弃,对无符号数,高位补0。

     在基础知识方面需要注意的是,在计算机中二进制数都是以补码的形式存在的,方便机器进行运算,正数的原码和补码是相同的,而负数的补码需要原码进行取反加一,在位运算时需要多加注意

    有位大神将位运算的使用总结成一句口诀:

    清零取反要用与,某位置一可用或

    若要取反和交换,轻轻松松用异或

    接下来就是位运算的各种使用技巧:

    (1) 按位与-- &

    1 清零特定位 (mask中特定位置0,其它位为1,s=s&mask)

    2 取某数中指定位 (mask中特定位置1,其它位为0,s=s&mask)

    (2) 按位或-- |

    常用来将源操作数某些位置1,其它位不变。 (mask中特定位置1,其它位为0 s=s | mask)

    (3) 位异或-- ^

    1 使特定位的值取反 (mask中特定位置1,其它位为0 s=s^mask)

    2 不引入第三变量,交换两个变量的值 (设 a=a1,b=b1)

    目 标          操 作              操作后状态

    a=a1^b1        a=a^b              a=a1^b1,b=b1

    b=a1^b1^b1      b=a^b              a=a1^b1,b=a1

    a=b1^a1^a1      a=a^b              a=b1,b=a1

    位运算实际应用:

    (1) 判断int型变量a是奇数还是偶数

    a&1  = 0 偶数

    a&1 =  1 奇数

    (2) 取int型变量a的第k位 (k=0,1,2……sizeof(int)),即a>>k&1

    (3) 将int型变量a的第k位清0,即a=a&~(1 << k)

    (4) 将int型变量a的第k位置1, 即a=a|(1 << k)

    (5) int型变量循环左移k次,即a=a < >16-k  (设sizeof(int)=16)

    (6) int型变量a循环右移k次,即a=a>>k|a < <16-k  (设sizeof(int)=16)

    (7)整数的平均值

    对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的,我们用如下算法:

    int average(int x, int y)  //返回X,Y 的平均值

    {

    return (x&y)+((x^y)>>1);

    }

    (8)判断一个整数是不是2的幂,对于一个数 x >= 0,判断他是不是2的幂

    boolean power2(int x)

    {

    return ((x&(x-1))==0)&&(x!=0);

    }

    (9)不用temp交换两个整数

    void swap(int x , int y)

    {

    x ^= y;

    y ^= x;

    x ^= y;

    }

    (10)计算绝对值

    int abs( int x )

    {

    int y ;

    y = x >> 31 ;

    return (x^y)-y ;        //or: (x+y)^y

    }

    (11)取模运算转化成位运算 (在不产生溢出的情况下)

    a % (2^n) 等价于 a & (2^n - 1)

    (12)乘法运算转化成位运算 (在不产生溢出的情况下)

    a * (2^n) 等价于 a < < n

    (13)除法运算转化成位运算 (在不产生溢出的情况下)

    a / (2^n) 等价于 a>> n

    例: 12/8 == 12>>3

    (14) a % 2 等价于 a & 1

    (15) if (x == a) x= b;

    else x= a;

    等价于 x= a ^ b ^ x;

    (16) x 的 相反数 表示为 (~x+1)

    "奇技淫巧" :


    技巧一:用于消去x的最后一位的1

    1 x & (x-1)
    2 x = 1100
    3 x-1 = 1011
    4 x & (x-1) = 1000

    1.1.应用一 用O(1)时间检测整数n是否是2的幂次.
    思路解析:N如果是2的幂次,则N满足两个条件。
    1.N>0
    2.N的二进制表示中只有一个1
    一位N的二进制表示中只有一个1,所以使用N&(N-1)将唯一的一个1消去。
    如果N是2的幂次,那么N&(N-1)得到结果为0,即可判断。


    1.2.应用二 计算在一个 32 位的整数的二进制表示中有多少个 1.
    思路解析:
    由 x & (x-1) 消去x最后一位知。循环使用x & (x-1)消去最后一位1,计算总共消去了多少次即可。


    1.3.将整数A转换为B,需要改变多少个bit位
    思路解析
    这个应用是上面一个应用的拓展。
    思考将整数A转换为B,如果A和B在第i(0<=i<32)个位上相等,则不需要改变这个BIT位,如果在第i位上不相等,则需要改变这个BIT位。所以问题转化为了A和B有多少个BIT位不相同。联想到位运算有一个异或操作,相同为0,相异为1,所以问题转变成了计算A异或B之后这个数中1的个数。


    技巧二 使用二进制进行子集枚举
    应用.给定一个含不同整数的集合,返回其所有的子集
    样例
    如果 S = [1,2,3],有如下的解:
    [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2] ]

    思路
    思路就是使用一个正整数二进制表示的第i位是1还是0,代表集合的第i个数取或者不取。所以从0到2n-1总共2n个整数,正好对应集合的2^n个子集。

     1 S = {1,2,3}
     2 N bit Combination
     3 0 000 {}
     4 1 001 {1}
     5 2 010 {2}
     6 3 011 {1,2}
     7 4 100 {3}
     8 5 101 {1,3}
     9 6 110 {2,3}
    10 7 111 {1,2,3}

    技巧三.a^b^b=a
    3.1.应用一 数组中,只有一个数出现一次,剩下都出现三次,找出出现一次的。
    问题
    Given [1,2,2,1,3,4,3], return 4

    解题思路
    因为只有一个数恰好出现一个,剩下的都出现过两次,所以只要将所有的数异或起来,就可以得到唯一的那个数。

     1 #include<stdio.h>
     2 int main()
     3 {
     4     int a[7]={1,2,2,1,3,4,3};
     5     int ans=0;
     6     for(int i=0;i<7;i++){
     7         ans^=a[i];
     8     }
     9     printf("%d
    ",ans);
    10 }

    3.2.应用二 数组中,只有一个数出现一次,剩下都出现三次,找出出现一次的。(还是很蒙蔽)
    问题
    Given [1,1,2,3,3,3,2,2,4,1] return 4

    解题思路
    因为数是出现三次的,也就是说,对于每一个二进制位,如果只出现一次的数在该二进制位为1,那么这个二进制位在全部数字中出现次数无法被3整除。
    模3运算只有三种状态:00,01,10,因此我们可以使用两个位来表示当前位%3,对于每一位,我们让Two,One表示当前位的状态,B表示输入数字的对应位,Two+和One+表示输出状态。

    0 0 0 0 0
     0 0 1 0 1
     0 1 0 0 1
     0 1 1 1 0
     1 0 0 1 0
     1 0 1 0 0
     One+ = (One ^ B) & (~Two)
     Two+ = (~One+) & (Two ^ B)
     1 #include<stdio.h>
     2 
     3 void findNum(int *a,int n)
     4 {
     5     int ans=0;
     6     int bits[32]={0};
     7     for(int i=0;i<n;i++){
     8         for(int j=0;j<32;j++){
     9             bits[j]+=((a[i]>>j)&1);
    10         }
    11     }
    12     for(int i=0;i<32;i++){
    13         if(bits[i]%3==1) ans+=1<<i;
    14     }
    15     printf("%d
    ",ans);
    16 }
    17 int main()
    18 {
    19     int a[10]={1,1,2,3,3,3,2,2,4,1};
    20     findNum(a,10);
    21 }

    3.3.应用三 数组中,只有两个数出现一次,剩下都出现两次,找出出现一次的
    问题
    Given [1,2,2,3,4,4,5,3] return 1 and 5

    解题思路
    有了第一题的基本的思路,我们不妨假设出现一个的两个元素是x,y,那么最终所有的元素异或的结果就是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 }

    2019-05-07 12:05:42 编程小菜鸟自我反省,大佬勿喷,谢谢!!!



  • 相关阅读:
    [BZOJ1492] [NOI2007]货币兑换Cash 斜率优化+cdq/平衡树维护凸包
    [BZOJ2638] 黑白染色
    [BZOJ2006] [NOI2010]超级钢琴 主席树+贪心+优先队列
    [BZOJ3698] XWW的难题 网络流
    [BZOJ2151] 种树 贪心
    js中的闭包理解一
    HTML5 input placeholder 颜色修改示例
    26 个 jQuery使用技巧
    JS原型与原型链(好文看三遍)
    文字和图片垂直居中
  • 原文地址:https://www.cnblogs.com/xgmzhna/p/10824640.html
Copyright © 2011-2022 走看看