高效面试之位运算

本文探讨了位运算在解决计算问题中的高效性,特别是如何利用位运算进行加法和求绝对值的操作。通过举例说明,阐述了如何通过按位与、按位异或来实现加法,以及如何通过特定的位操作获取正数和负数的绝对值,避免溢出问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.技巧
1.特殊数&或者!
2.本身异或为0,与0异或为本身,满足交换律
例:不借助第三数 交换两数
3.取反加1
求相反数
4.巧妙分组处理(16bit位的数)
分为8组分组需要与0xAAAA或者0x5555相与,交换位置需要移动1位)
a&0xAAAA                    1010,1010,1010,1010
 (a&0xAAAA)>>1            1010,1010,1010,1010  
a&0X5555                      0101,0101,0101,0101
(a&0X5555)<<1          0101,0101,0101,0101 
 (a&0xAAAA)>>1  | (a&0X5555)<<1

分为4组分组需要与0xCCCC或者0x3333相与,交换位置需要移动2位)
a&0xCCCC                       1100,1100,1100,1100.
(a&0xCCCC)>>2                  1100,1100,1100,1100
0x3333                              0011,0011,0011,0011
                                     0011,0011,0011,0011
分为2组分组需要与0xF0F0或者0x0F0F相与,交换位置需要移动4位)
0xF0F0                     1111,0000,1111,0000.    
                                         1111,0000,1111,0000. 
                                 0000,1111,0000,1111
                         0000,1111,0000,1111
分为1组分组需要与0xFF00或者0x00FF相与,交换位置需要移动8位)
  0XFF00               1111,1111,0000,0000.   
                 0000,0000,1111,1111
5.异或模拟加法

0&0=0;0&1=0; 1&0=0; 1& 1 = 1

0|0=0;     0|1=1;      1|0=1;     1|1=1;

0^0=0;   0^1=1;     1^0=1;    1^1=0;

异或运算是不是和二进制加法很像,缺少进位而已?后面用逻辑运算实现加法讲解

6.常用等式
(1)-n=~(n-1)=~n+1 //求绝对值使用
(2)获取n的二进制中最后一个1:n&(-n)或者n&~(n-1),例如,n=010100,-n=101100,n&(-n)=000100//乘法运算中应用
(3)去掉n的二进制中最后一个:n&(n-1),例如,n=010100,n-1=010011,n&(n-1)=010000 //求二进制中1的个数使用
二.例子
1. 高低位交换
i= (i<<8) | (i>>8);//核心代码,把i左移N位,i右移M位,进行位运算,是一个比较常用的方式    
2.二进制逆序
用1个字节来想,比较好像
/*以1个字节为整体,进行交换*/
   a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1); //1010,1010,1010,1010.    0101,0101,0101,0101
/*以2位为整体,进行交换*/ 
   a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2);  //1100,1100,1100,1100.    0011,0011,0011,0011
/*以4位为整体,进行交换*/  
   a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4);    //1111,0000,1111,0000.    0000,1111,0000,1111
/*以8位为整体,进行交换*/ 
    a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8);   //1111,1111,0000,0000.    0000,0000,1111,1111
3.  二进制中1的个数
 

  x=((x & 0XAAAA) >> 1)+(( x & 0X5555 )); //2位为1组,共8组,高1位和低1位相加。   
  x=((x & 0XCCCC) >> 2)+(( x & 0X3333 ));//4位为1组,共4组,分成高2位和低2位相加   
  x=((x & 0XF0F0) >> 4)+(( x & 0X0F0F ));//8位为1组,共2组,分成高4位和低4位相加   
  x=((x & 0XFF00) >> 8)+(( x & 0X00FF )); //16位为1组,共1组,分成高低组;高8位,与低8位相加 
4.不借助第三个数交换两数
void Swap(int &a, int &b)
{
 if (a != b)
 {
          a ^= b;
          b ^= a;
          a ^= b;
 }
}
5.缺失的数
data[N]={1,2,1,2,3,3,5,4,5};
         i^=data[i];   //自身异或为0

题目收集:
1.最有效的计算2*8的方法是什么?
2<<3
引申:如何快速求取一个整数的7倍?
(x<<3)-x
注意-的优先级高于<<

2.如何实现位操作求两个数的平均值?
(x+y)/2
解:
(x&y)+((x^y)>>1)
x&y表示的是取出x与y二进制位数中都为‘1’的所有位。(0&0=0;0&1=0; 1&0=0; 1& 1 = 1)
x^y表示的是x与y中有一个为'1'所有位,右移1位相当于执行除以2。(0^0=0;   0^1=1;     1^0=1;    1^1=0;)
整个表达式实际分为2部分,第一部分都是'1'的部分,直接相加。第二部分,有一个为1 的,加起来除以2.

每个二进数都可以分解为各个位与其权的乘积的和,把两个数的分解为这样的多项式进行相加。考虑两个数的二进制序列中相同的位与不同的位:将相同的位进行相加,结果等于两数按位与的结果的两倍;将不同的位进行相加,其结果等于按位异或的结果。
      举例,10的二进制是:1010.     6的二进制是:0110.

      1010 = 1*2的3次方 + 0*2的2次方 + 1*2的1次方 + 0*2的0次方

       0110 = 0*2的3次方 + 1*2的2次方 + 1*2的1次方 + 0*2的0次方

那么,算1010 + 0110时,就可以让对应的项分别相加(即幂相同的项分别相加),再求总和。

这样的话,如果某次幂,两个系数一个为0,一个为1,那么相加之后一定为1*2的n次方,而这整好可以通过两个数的按位异或得到;如果某次幂,两个系数均为1,相加之后为2*2的n次方,除以2,应该为1*2的n次方.而两个数按位与记为所求。

这种方法避免了应用Avg=(x+y))/2时,x+y造成的溢出

引申:如何利用位运算求计算式的绝对值?
int myABS(int x)
{
    int y;
    y=x>>31;//负数右移31,为0xffffffff。正数为0x00000000
    return (x^y)-y;
}

如果iNum是正数:
         temp = temp >> 31; //temp = 0
         out = out ^ temp; //
0异或不变
         out = out - temp; //
0不变
 
out
的结果就是iNum,即正数的绝对值是其本身,没问题
 
如果iNum是负数:
         temp = temp >> 31; //temp = oxffffffff
         out = out ^ temp; //out
iNum求反
         out = out - temp; // 
此时temp = 0xffffffff = -1, 所以out = out + 1
把一个负数的补码连符号位求反后再加1,就是其绝对值了。比如对于-2来说:

原码

 反码

补码

补码全求反

再加1

备注

10000010

11111101

11111110

00000001

00000010



大家可以看到第一个与最后一个数只有符号位不同,也就实现了求其绝对值。

任何数与1111(全1)异或,其实就是把x中的0和1进行颠倒。

3.如何求整形数的二进制表示的1的个数?
方法一:
int func(int x)
{
    int count;
    while(x)
    {
            count++;
            x=x&(x-1);
    }
}
有几位bit为1,程序循环几次。
从右向左数,找到第一个1,把1后面的所有的数字都变为0。如x=1000110,x&(x-1)=1000100就是把x右边的第一个1后面的数变为0
方法二:
int func(unsigned int x)
{
    int count;
    while(n)
    {
            count+=x&0x1u;
            x>>=1;
    }
}
循环次数为x的bit位数。这种方法的缺陷就是,1比较稀疏的时候,效率会低。


4.如果不用sizeof,如何判断系统是16位还是32位?
解:
16位机器,int为2字节,最大值为65535
unsigned int a=~0;
if(a>65536)
    printf("32位");

5.如何判断大段,小段?
int checkCPU()
{
    unsigned short data=0x1122;
    unsigned char *p=(unsigned char*)&data;
    return (*p==0x22);
}
定义1个short型data,用一个char*指针,指向data.判断第一个字节的内容

6考虑n位二进制,有多少个数中不存在两个相邻的1?
定义n时为a(n)个。那么,
1)n位为0时,有a(n-1)个数。
2)n为1时,第n-1位必然为0,有a(n-2)个数,
因此有a(n)=a(n-1)+a(n-2)
满足斐波拉契数列
a(n)=a(n-1)+a(n-2)

7.不用除法操作如何实现两个正整数的除法?
采用移位操作

引申:用逻辑运算实现加法运算?
int add(int num1,int num2)
{
    int sum=0;
    int num3=0;
    int num4=0;
    while((num1&num2)>0)
    {
        num3=num1^num2;//异或模拟二进制加法,不处理进位
        num4=num1&num2;//与运算1&1=1,再移位,来处理进位。
        num1=num3;
        num2=num4<<1;
    }
    sum=num1^num2;
    return sum;
}
递归更简洁:
int add(int num1,int num2)
{
        if(0==num2) return num1;//收敛条件
        int sumtemp=0;
        int carry=0;
        sumtemp=num1^num2;//异或模拟二进制加法,不处理进位
        carry=(num1&num2)<<1;//与运算1&1=1,再移位,来处理进位。
        return add(sumtemp,carry);
    }
    sum=num1^num2;
    return sum;
}
用逻辑运算实现乘法运算?
二进制 1011*1010
分成:1011*1000+1011*0010=1011<<3+1011<<1
int multipy(int a,int b)
{
    bool neg=(b<0);
    if(b<0) b=-b;
    int sum=0;
    map<int,int>bit_map;
    for(int i=0;i<32;i++)
        bit_map.insert(pair<int,int>(1<<i,i));
    while(b>0)
    {
        int last_bit=bit_map[b&~(b-1)];
        sum+=(a<<last_bit);
        b&=b-1;
    }
    if(neg)
        sum=-sum;
    return sum;
}
实现求整求余数运算?
n/32    即n>>5
n % 32 即 n & (32-1)   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值