您的位置:首页 > 职场人生

高效面试之位运算

2014-10-26 00:58 155 查看
一.技巧

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
={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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: