【演算法】位元位計算(A+B Problem)-位運算子介紹、位運算實現加減乘除
問題描述
問題:計算A+B,不適用“+”運算子(LintCode 1.A + B Problem)
注意:A B均為32位整數,可使用位元位計算
解決思路
演算法示例
程式碼如下:(通過LintCode測試)
class Solution {
public:
/*
* @param : An integer
* @param : An integer
* @return: The sum of a and b
*/
int aplusb(int a, int b) {
// write your code here
int s, c;
while(b != 0)
{
s = a^b;
c = (a&b)<<1;
a = s;
b = c;
}
return a;
}
};
知識整理
位運算
位運算是按照二進位制進行的運算,c語言提供6個位運算操作符,這些操作符適用於整型運算元。具體6個操作符如下:
符號 | 名稱 | 含義 |
---|---|---|
& | 按位與 | 兩個相應的二進位制位都為1,該位的結果值為1 |
| | 按位或 | 兩個相應的二進位制位中只要有一個為1,該位的結果值為1 |
^ | 按位異或 | 兩個相應的二進位制位值相同則為0,否則為1 |
~ | 取反 | 一元運算子,對一個二進位制數按位取反,將0變1,將1變0 |
<< | 左移 | 將一個數的各二進位制位全部左移N位,右補0 |
>> | 右移 | 將一個數的各二進位制位右移N位,移到右端的低位被捨棄,無符號數高位補0 |
1.按位與 &
參與運算的兩個數按照二進位制位依次進行與運算,即兩位都是1結果為1,否則為0。例如:3&6,換算為8位二進位制數(高位補0),為
00000011&
00000110=
00000010
按位與可用於:清零、保留指定位。
清零:想將原數清零,只需要原數中所有為1的位,在新數位相應為0,兩數&操作即可為00000000。
保留指定位:想保留原數的第n位,只要新數的第n位保證為1,兩數&操作即可保留原數的第n位,想保留多位同理。
2.按位或 |
參與運算的兩個數對應的二進位制位只要有一個為1,結果為1。例如
00011001 |
01001010=
01011011
按位或可用於:將原數的某些位定值為1,只需要與它進行按位或操作的新數對應的位數為1即可。
3.按位異或 ^
參與運算的兩個數對應二進位制位相同為0,不同為1。例如:
00110011 ^
01000110 =
01110101
按位異或可用於:指定位翻轉(與1異或)、保留原值(與0異或)、交換兩數。
交換兩數:交換a和b:
a=a∧b;
b=b∧a;
a=a∧b; 交換成功
4.取反 ~
二進位制位1變0,0變1。
5.左移 <<
根據指定位數將原數各個二進位制位左移指定位,高位左移溢位則捨棄,低位空位則用0補齊。
當該數左移時溢位捨棄的高位中不包含1時,左移類似乘法,左移n位相當於該數乘以2^n。
6.右移 >>
根據指定位數將原數各個二進位制位右移指定位,移到右端的低位被捨棄。左端高位,如果無符號數,補0。對於有符號數,移入0稱為邏輯右移,移入1的稱為算術右移。
位運算實現加減乘除
1.位運算實現加法
無論哪種進位制的加法,其核心都是“和”、“進位”。位運算有一個特點,首先是:
位運算的異或運算與“和”一致:
異或 1^1=0;1^0=1;0^0=0
求和 1+1=0;1+0=1;0+0=0
位運算的與運算與“進位”一致:
位與 1&1=1;1&0=0;0&0=0
進位 1+1=1;1+0=0;0+0=0
綜合上述兩個特點實現加法,程式碼如下:(分析詳見註釋)
int add(int a, int b) //遞迴形式
{
if(b==0) //遞迴結束條件:如果右加數為0,即不再有進位,結束運算
return a;
int s = a^b;
int c = (a&b)<<1; //進位左移1位
return add(s, c); //把'和'和'進位'相加
}
int add(int a, int b) //迴圈形式
{
int s, c;
while(b != 0)
{
s = a^b;
c = (a&b)<<1;
a = s;
b = c;
}
return a;
}
2.位運算實現減法
減法實質使用加法實現的。先把減數求負,然後做加法。對一個數求負的方法是:將該數連符號位一起取反,然後加一。
程式碼如下:(先求負,在相加)
int negtive(int i) //求負
{
return add(~i, 1);
}
int subtraction(int a, int b) //減法運算:利用求負操作和加法操作
{
return add(a, negtive(b));
}
3.位運算實現乘法
乘法需要考慮符號位,需要進行符號位的提取,以及負數的取正。乘法的實現也可藉助加法,a*b可以看成b個a相加,通過迴圈實現,程式碼如下:(此法慢,其他方法不做贅述)
int getsign(int i){ //取一個數的符號,看是正還是負
return (i>>31);
}
int bepositive(int i){ //將一個數變為正數,如果本來就是正,則不變;如果是負,則變為相反數。注意對於-2147483648,求負會溢位。
if(i>>31)
return negtive(i);
else
return i;
}
int multiply(int a, int b){ //迴圈法實現加法
bool flag = true;
if(getsign(a) == getsign(b)) //積的符號判定
flag = false;
a = bepositive(a);//先把乘數和被乘數變為正數
b = bepositive(b);
int ans = 0;
while(b){
ans = add(ans, a);
b = subtraction(b, 1);
}
if(flag)
ans = negtive(ans);
return ans;
}
4.位運算實現除法
除法可藉助減法實現,從被除數上減去除數,看能減多少次之後變得不夠減。程式碼如下:(此法慢,其他方法不做贅述)
int division(int a, int b){
if(b==0)
return 0;
bool flag = true;
if(getsign(a) == getsign(b)) //積的符號判定
flag = false;
a = bepositive(a);
b = bepositive(b);
int n = 0;
a = subtraction(a, b);
while(a>=0){
n = add(n, 1);
a = subtraction(a, b);
}
if(flag)
n = negtive(n);
return n;
}
位運算常用技巧
1.計算二進位制數中1的個數
通過與初始值為1的標誌位進行與運算,判斷最低位是否為1;然後將標誌位左移,判斷次低位是否為1;一直這樣計算,直到將每一位都判斷完畢。
int countOf1(int num){
int count = 0;
unsigned int flag = 1;
while(flag){
if(num & flag)
count++;
flag = flag << 1;
}
return count;
}
2.判斷某數是否為2的n次方
一個數是2的n次方,則這個數的最高位是1,其餘位為0。
bool is2Power(int num){
bool flag = true;
num = num & (num - 1); //計算num和num - 1的與的結果
if(num) //如果結果為0,則不是2的n次方
flag = false;
return flag;
}
3.判斷某數的奇偶性
判斷奇偶性,實質是判斷最後一位是否是1。
// 判斷一個數的奇偶性.返回1,為奇數;返回0,為偶數
bool isOdd(int num){
return num & 1 == 1;
}
4.交換兩數
交換a和b:
a=a∧b;
b=b∧a;
a=a∧b;
5.求絕對值
將原數n右移31位,可以獲得n的符號。
// 若n為正數,得到0;若n為負數,得到-1
int myAbs(int n){
return (n ^ n >> 31) - (n >> 31);
}
6.求平均值
int getAverage(int m,int n){
return (m + n) >> 1;
}