位运算汇总

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

基础原理

位操作实现

  • n&(n-1)的妙用:消除最低位1
    计算位向量中1的个数
/*
n&n-1的妙用。
n与n-1的区别在于,对于n,最低位1开始一直到右,和n-1,完全相反

n   = 10-100
n-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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值