位运算 - 应用篇

基础篇中对各种位运算操作进行了详细的介绍说明,在具备基础篇的知识基础上,学习了解一些位运算的奇淫技巧,能够更好的掌握位运算知识,同时在一些算法题中,也可以有更开阔的视野和题解。

斯坦福计算机资料整理:http://graphics.stanford.edu/~seander/bithacks.html#OperationCounting

CHAR_BIT is the number of bits per byte (normally 8).

奇偶数判断

从十进制转化为二进制的转化方式可以直观的看到,当一个整数除2,最终的余数是0,这个整数是-偶数。相反,余数为1,这个数是-奇数。

     偶数:   a & 1  == 0
     奇数     a & 1  == 1
移位操作实现乘除

将数 a 左移1位,等价于 a乘以2; 右移1位,等价于 a除以2.

    a >>= 1     ----      a = a / 2;
    a <<= 1     ----      a = a * 2;
整数数值交换

异或,使用异或实现两个整数的交换。

    void swap(int& a, int& b) {
        a ^= b;         // a = a ^ b;
        b ^= a;         // b = b ^ (a ^ b) = a
        a ^= b;         // a = (a ^ b) ^ a = b
    }

    // 或者使用加减法实现
    void swap(int& a, int& b) {
        a = a + b;      
        b = a - b;
        a = a - b;
    }
正负数转换

正数取反加一,变负数;负数取反加一,变正数。

    int  reverely(int a) {
        return (~a + 1);
    }
    
        1111 1111 1111 1011     -5
    ----------------------------------
      ~ 0000 0000 0000 0100     ~(-5)
    ----------------------------------
     +1 0000 0000 0000 0101     5
    ----------------------------------
    
    // 正数取反加1,变成其对应的负数(补码表示)
    // 负数取反加1,变成其对应的正数(原码)
求绝对值

正整数的绝对值是其本身,负数的绝对值正好可以对其取反加1获取,通过判断符号位来确定数值的正负值,返回其绝对值。(个人感觉只是一种思路,直接判断与0的大小进行返回更直观些)

    // 根据正负值进行
    unsigned int abs(int a) {
        return (a >= 0) ? a : -a;
    }
    
    // 根据符号位值计算
    unsigned int abs(int a) {
        int r = a >> sizeof(int) * CHAR_BIT - 1;
        return r == 0 ? a : ~a + 1;
    }
    
    // 对于任何数于0异或,保持不变,与-1异或,等价于对此数取反,上述函数可以转化为
    unsigned int abs(int a) {
        int mark = a >> sizeof(int) * CHAR_BIT - 1;
        return ((a ^ mark) -mark)
    }
高低位交换

给定一个整数,求交换高低位之后的数

    例如 十进制 10   0000 1010
    交换高低位位置   1010 0000  
    
    int x = (a >> 4) | (a << 4);
    
    // 交换字节的高低位并不是一个很常见的问题,一些算法题中也会有表现。
二进制位逆序

字符串或数组的逆序,可以依次交换字符串或数组的首位数据,实现逆序。二进制中实现逆序,使用高低位交换更方便处理

举例说明:

将无符号数的二进制表示进行逆序,求取逆序后的结果。

    数字: 35874    1000 1100 0010 0010‬
    逆序:  17457    0100 0100 0011 0001
    
    ① 以每2位为一组,组内进行高低位交换
    
    交换前:     10 00 11 00 00 10 00 10
    交换后:    01 00 11 00 00 01 00 01
    
    ② 以①的结果,进行每4位一组,组内以2位高低位交换
    交换前:     0100 1100 0001 0001
    交换后:    0001 0011 0100 0100
    
    ③ 同理,八位一组,组内以四位进行高低位交换
    交换前:    00010011 01000100
    交换后:    00110001 01000100
    
    ④ 同理,十六位一组..
    交换前:    0011000101000100
    交换后:    0100010000110001
    
    2位一组,进行交换,实现方式如下:
    // 取数值的奇数位,其余位用0填充: a & 0xAAAA
    // 取数值的偶数位,其余位用0填充: a & 0x5555
    // 实现高低位交换: 
    a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1)
    // 同理 ②③④步为:
    a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2)
    a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4)
    a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8)
    
  • 面试题
    对一个字节数据,逐个交换其高低位,例如:1101 1010, 经过交换,变成 0101 1011.
    
    a = (a << 4) | (a >> 4);                        1010 1101    
    a = ((a << 2) & 0xcc) | ((a >> 2) & 0x33);      1010 0111
    a = ((a << 1) & 0xaa) | ((a >> 1) & 0x55);      0101 1011
    
    可以抽象的理解:1234 5678
    a = 5678 1234
    a = 7856 3412
    a = 8765 4321
    以此实现字节数据的诸位交换。
二进制数值1的个数

统计数值二进制位1的个数,可以分别获取二进制位中的数,然后统计1出现的次数。

    // 直观的可以使用循环来计算
    unsigned int bitCounter(unsigned int a) {
        unsigned int c = 0;
        while(a) {
            c += a & 1;
            a >>=1;
        }
        return c;
    }
    
    // 相对高效的计算
    unsigned int bitCounter(unsigned int a) {
        unsigned int c = 0;
        while(a) {
            a &= (a - 1)
            ++c;
        }
        return c;
    }
    
    // 比如:2048  二进制 1000 0000 0000 , 2048 -1 0111 1111 1111,
    // 第二种算法中只需要一次的循环就可以求出其中的位。
计算整数的符号
   sign = v >> sizeof(int) * CHAR_BIT - 1;  // if v < 0  then -1, else 0 
   
   sign = +1 | (v >> (sizeof(int) * CHAR_BIT - 1));  // if v < 0 then -1, else +1
   
   sign = 1 ^ ((unsigned int)v >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then 0, else 1
计算两个数的最大值、最小值
    // 位运算方法一:
    int min(int x, int y) {
        return y ^ ((x ^ y) & -(x < y));
    }
    // if  x < y = true, y ^ ((x ^ y) & -1) = x
    // if  x < y = false, y ^ ((x ^ y) & 0) = y
    
    
    int max(int x, int y) {
        return x ^ ((x ^ y) & -(x < y));
    }
    // if  x < y = true, x ^ ((x ^ y) & -1) = y
    // if  x < y = false, x ^ ((x ^ y) & 0) = x
判断一个数是不是2的幂次方

leetcode 题库试题

比较笨的方法,观察2的幂次值: 0001, 0010, 0100, 1000 …

    bool isPowerOfTwo(int val) {
        if (val <= 0)  return false;
        return (val & (val - 1) == 0);
    }
    
    // return val && !(val & (val - 1))

时间有限,工作之余整理,文章开头的斯坦福大学资料总结比较全面,有兴趣的可以直接阅读。根据个人理解所写,如果存在理解偏差或者错误请指出,非常感谢,也希望对你的学习有所帮助。

你可能感兴趣的:(位运算,C++基础知识,Linux,位运算,计算机,应用)