位运算汇总

来源:互联网 发布:单片机射频通讯 编辑:程序博客网 时间:2024/06/07 16:19

本文主要参考了这篇博文[位操作基础篇之位操作全面总结],并且复习了深入理解计算系统第二章的内容。

基础原理

位操作实现

  • n&(n-1)的妙用:消除最低位1
    计算位向量中1的个数
/*n&n-1的妙用。n与n-1的区别在于,对于n,最低位1开始一直到右,和n-1,完全相反n   = 10-100n-1 = 10-011因此,n&n-1可以把n的最低位1变成0下面这个例子计算位向量中1的个数。*/class Solution {public:    int hammingWeight(uint32_t n) {        int cnt = 0;        while( n )        {            ++cnt;            n &= n-1;        }        return cnt;    }};
/*判断一个数是否是2的幂,如果是2的幂。位向量当中,只有1个1。所以,用n&(n-1)干掉之后,就没了。*/class Solution {public:    bool isPowerOfTwo(int n) {        return ( n > 0 && !(n&(n-1)) );    }};
  • 寻找只出现1次的一个数
    问题扩展:(小米2013校招笔试题)一个数组里,除了三个数是唯一出现的,其余的都出现偶数个,找出这三个数中的任一个。比如数组元素为【1, 2,4,5,6,4,2】,只有1,5,6这三个数字是唯一出现的,我们只需要输出1,5,6中的一个就行。

    思路:之前做的是两个数分开,思路一样。现在三个数分开,只让你输出其中的一个数。对于出现2次的,xor都没了。对于出现一次的三个数,因为三个数不同,所以有一位三个数的位向量不同。一定是两个0,一个1。此时,xor之后,肯定会把一个数字分出来。然后用这位当mask,把这个数分到一组。然后xor就得到了。

int find( std::vector<int>& nums ){    // get the a^b^c    int sz = nums.size();    int ret = 0;    for( int i = 0; i < sz; ++i )    {        ret ^= nums[i];    }    // get the mask    int mask = 1;    while( !(ret&mask) )    {        mask = mask << 1;    }    // separate    std::vector<int> ret_vec;    for( int i = 0; i < sz; ++i )    {        if( nums[i] & mask )            ret_vec.push_back( nums[i] );    }    // get the ans    sz = ret_vec.size();    int ans = 0;    for( int i = 0; i < sz; ++i )    {        ans ^= ret_vec[i];    }    return ans;}/*【1,2,4,5,6,4,2】得到6*/
  • 二进制中1的个数
    思路:当然这个题目的实现方法非常多啊。说两种我觉得挺简单的。
    • 方法1:移位。每次看最低位是否为1.若是,则加1。从那边移动都可以,对于正负数也都没有问题。前提是参数必须是无符号数。即用无符号数的视角审视这段位向量。无符号数是逻辑移位,不影响。
    • 方法2:用掩码。看看每一位是否为1,和mask相与后不为0即改位为1.
// 移位int number_of_1bits( uint32_t n ){    int ans = 0;    do    {        ans += (n&1);        n >>= 1;    }while(n);    return ans;}
//掩码int number_of_1bits1( uint32_t n ){    uint32_t mask = 0x80000000;    int ans = 0;    do    {        ans += (( mask & n )?1:0);        mask >>= 1;    }    while(mask);    return ans;}
  • 加法实现
    思路:

    • 计算不带进位加法( a ^ b )
    • 计算进位( a & b )
    • 累加这两部分的和(???)

    思路大致如上面的步骤,问题是本省需要实现加法,但是第三步又要用到加法,所以第三步的实现还是通过前两部分。直到没有进位,那么此时的加法退化成不带进位加法,用xor实现即可。

    代码

    int getSum(int a, int b) {        int carry = (a&b)<<1; // 进位        int ret = a^b;        // 不带进位加法        while( carry )        {            int new_carry = (carry & ret)<<1;            int new_ret = carry ^ ret;            carry = new_carry;            ret = new_ret;        }        return ret;    }
  • 二进制求逆序
    描述:

    我们知道如何对字符串求逆序,现在要求计算二进制的逆序,如数34520用二进制表示为:
    10000110 11011000
    将它逆序,我们得到了一个新的二进制数:
    00011011 01100001
    它即是十进制的7009。

    思路:

    总体的思路是分治,和归并排序的思路一样。
    对于一个字符串 s = “abcdefgh”;给出一种分治的求逆方法。
    分治:先把abcd置逆 -> dcba
    分治:再把edgh置逆 -> hgfe
    归并:hgfe dcba
    剩下递归就好了。递归分治到最底层是两两交换。然后依次向上即可。
    但是问题是,怎么实现?

    具体的思路:首先分别取出需要交换的位,高位右移,低位左移,然后做或运算。完成交换。一下是一个具体步骤。
    原数:10000110 11011000
    第一步:10 00 01 10 11 01 10 00
    -> 01 00 10 01 11 10 01 00
    四个四个
    第二步:0100 1001 1110 0100
    -> 0010 1001 0111 0010
    八个八个
    第三步:00101001 01110010
    -> 10010100 01001110
    十六个
    第四步:10010100 01001110
    -> 0111001000101001

    代码:

uint16_t reverse_bits( uint16_t val ){    val = ( ( val & 0xAAAA ) >> 1 )|( ( val & 0x5555) << 1 );    val = ( ( val & 0xCCCC ) >> 2 )|( ( val & 0X3333) << 2 );    val = ( ( val & 0xF0F0 ) >> 4 )|( ( val & 0x0F0F ) << 4 );    val = ( ( val & 0XFF00 ) >> 8 )|( ( val & 0X00FF ) << 8 );    return val;}
  • 高低位交换
    描述:

    给出一个16位的无符号整数。称这个二进制数的前8位为“高位”,后8位为“低位”。现在写一程序将它的高低位交换。例如,数34520用二进制表示为:
    10000110 11011000
    将它的高低位进行交换,我们得到了一个新的二进制数:
    11011000 10000110
    它即是十进制的55430。

    思路:

    思路也非常简单啊,高八位左移,第八位右移,然后求或即可。

    代码:

uint16_t change_high_low( uint16_t val ){    return (val >> 8)^(val << 8);}

  • 判断奇偶
    思路:只需看最后一位即可

    代码

bool is_odd(int val){    return val&1;}
  • 交换两数
    思路:主要是利用xor的性质。a^a = 0, 所以a^a^b = b.
    代码
void swap( int& a, int& b ){    a ^= b;    b ^= a; // 注意这个顺序不能变,一定要先求b    a ^= b;}
  • 取反
    思路:在原操作数补码的基础上,直接取反 + 1即可(包括符号位)。至于我们课本里面学的那一套,那是在负数原码的基础上,符号位不变,取反 + 1。本质都一样。

    代码

int sign_reverse(int val){    return (~val+1);}
  • 取绝对值
    思路:这个题的思路秒。主要利用了一个性质 a ^ 0xffffffff = ~a , a^0 = a。对于负数而言,算术右移,所以sign = 0xffffffff(-1)

    代码

int sign_abs1(int val){    int sign = val >> 31; // 获得符号位    return ( (val ^ sign) - sign);}
1 0
原创粉丝点击