近日來在看書的過程中被這樣的一句話 假設hi和low是兩個整數,它們的值介於0到15之間,若是r是一個8位整數,且r的低四位與low各位上的數一致,而r的高4位與hi各位上的數一致,很天然會想到要這樣寫:c++
r = hi << (4 + low); 整的迷惑,說實話,位運算也學過,但使用最多的也就是在交換數值當中使用異或運算,而當我明白書中這句話裏表達式的含義時,深深被其中的精妙所折服,因而在網上搜集了關於位運算的一些使用技巧,記錄如下,以供後日參考(其中借鑑了csdn和百度上的一些問答,由於數量繁雜,記得不是太全,向這些博主表示衷心的感謝!):算法
首先仍是從最基礎的提及,寫程序位運算是必要的嗎,以個人理解並非,可是位運算正因爲其自己可以直接操做底層二進制數的特性,能提升近百分之60左右的運算效率,也是值得去學習和深刻研究的,在學校的學習中位運算,課堂的知識講解的比較少,把c/c++中 位運算 的基礎知識先羅列一下編程
運算符 | 含義 | 功能 |
& | 按位與 | 若是兩個相應的二進制位都爲1,則該位的結果值爲1;不然爲0。 |
| | 按位或 | 兩個相應的二進制位中只要有一個爲1,該位的結果值爲1。 |
∧ | 按位異或 | 若參加運算的兩個二進制位同號則結果爲0(假)異號則結果爲1(真) |
~ | 取反 | ~是一個單目(元)運算符,用來對一個二進制數按位取反,即將0變1,將1變0。 |
<< | 左移 | 左移運算符是用來將一個數的各二進制位所有左移N位,右補0。 |
>> | 右移 | 表示將a的各二進制位右移N位,移到右端的低位被捨棄,對無符號數,高位補0。 |
在基礎知識方面須要注意的是,在計算機中二進制數都是以補碼的形式存在的,方便機器進行運算,正數的原碼和補碼是相同的,而負數的補碼須要原碼進行取反加一,在位運算時須要多加註意數組
有位大神將位運算的使用總結成一句口訣:學習
清零取反要用與,某位置一可用或spa
若要取反和交換,輕輕鬆鬆用異或code
接下來就是位運算的各類使用技巧:blog
(1) 按位與-- &it
1 清零特定位 (mask中特定位置0,其它位爲1,s=s&mask)io
2 取某數中指定位 (mask中特定位置1,其它位爲0,s=s&mask)
(2) 按位或-- |
經常使用來將源操做數某些位置1,其它位不變。 (mask中特定位置1,其它位爲0 s=s | mask)
(3) 位異或-- ^
1 使特定位的值取反 (mask中特定位置1,其它位爲0 s=s^mask)
2 不引入第三變量,交換兩個變量的值 (設 a=a1,b=b1)
目 標 操 做 操做後狀態
a=a1^b1 a=a^b a=a1^b1,b=b1
b=a1^b1^b1 b=a^b a=a1^b1,b=a1
a=b1^a1^a1 a=a^b a=b1,b=a1
(1) 判斷int型變量a是奇數仍是偶數
a&1 = 0 偶數
a&1 = 1 奇數
(2) 取int型變量a的第k位 (k=0,1,2……sizeof(int)),即a>>k&1
(3) 將int型變量a的第k位清0,即a=a&~(1 << k)
(4) 將int型變量a的第k位置1, 即a=a|(1 << k)
(5) int型變量循環左移k次,即a=a < >16-k (設sizeof(int)=16)
(6) int型變量a循環右移k次,即a=a>>k|a < <16-k (設sizeof(int)=16)
(7)整數的平均值
對於兩個整數x,y,若是用 (x+y)/2 求平均值,會產生溢出,由於 x+y 可能會大於INT_MAX,可是咱們知道它們的平均值是確定不會溢出的,咱們用以下算法:
int average(int x, int y) //返回X,Y 的平均值
{
return (x&y)+((x^y)>>1);
}
(8)判斷一個整數是否是2的冪,對於一個數 x >= 0,判斷他是否是2的冪
boolean power2(int x)
{
return ((x&(x-1))==0)&&(x!=0);
}
(9)不用temp交換兩個整數
void swap(int x , int y)
{
x ^= y;
y ^= x;
x ^= y;
}
(10)計算絕對值
int abs( int x )
{
int y ;
y = x >> 31 ;
return (x^y)-y ; //or: (x+y)^y
}
(11)取模運算轉化成位運算 (在不產生溢出的狀況下)
a % (2^n) 等價於 a & (2^n - 1)
(12)乘法運算轉化成位運算 (在不產生溢出的狀況下)
a * (2^n) 等價於 a < < n
(13)除法運算轉化成位運算 (在不產生溢出的狀況下)
a / (2^n) 等價於 a>> n
例: 12/8 == 12>>3
(14) a % 2 等價於 a & 1
(15) if (x == a) x= b;
else x= a;
等價於 x= a ^ b ^ x;
(16) x 的 相反數 表示爲 (~x+1)
"奇技淫巧" :
技巧一:用於消去x的最後一位的1
1 x & (x-1) 2 x = 1100 3 x-1 = 1011 4 x & (x-1) = 1000
1.1.應用一 用O(1)時間檢測整數n是不是2的冪次.
思路解析:N若是是2的冪次,則N知足兩個條件。
1.N>0
2.N的二進制表示中只有一個1
一位N的二進制表示中只有一個1,因此使用N&(N-1)將惟一的一個1消去。
若是N是2的冪次,那麼N&(N-1)獲得結果爲0,便可判斷。
1.2.應用二 計算在一個 32 位的整數的二進制表示中有多少個 1.
思路解析:
由 x & (x-1) 消去x最後一位知。循環使用x & (x-1)消去最後一位1,計算總共消去了多少次便可。
1.3.將整數A轉換爲B,須要改變多少個bit位
思路解析
這個應用是上面一個應用的拓展。
思考將整數A轉換爲B,若是A和B在第i(0<=i<32)個位上相等,則不須要改變這個BIT位,若是在第i位上不相等,則須要改變這個BIT位。因此問題轉化爲了A和B有多少個BIT位不相同。聯想到位運算有一個異或操做,相同爲0,相異爲1,因此問題轉變成了計算A異或B以後這個數中1的個數。
技巧二 使用二進制進行子集枚舉
應用.給定一個含不一樣整數的集合,返回其全部的子集
樣例
若是 S = [1,2,3],有以下的解:
[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2] ]
思路
思路就是使用一個正整數二進制表示的第i位是1仍是0,表明集合的第i個數取或者不取。因此從0到2n-1總共2n個整數,正好對應集合的2^n個子集。
1 S = {1,2,3} 2 N bit Combination 3 0 000 {} 4 1 001 {1} 5 2 010 {2} 6 3 011 {1,2} 7 4 100 {3} 8 5 101 {1,3} 9 6 110 {2,3} 10 7 111 {1,2,3}
技巧三.a^b^b=a
3.1.應用一 數組中,只有一個數出現一次,剩下都出現三次,找出出現一次的。
問題
Given [1,2,2,1,3,4,3], return 4
解題思路
由於只有一個數剛好出現一個,剩下的都出現過兩次,因此只要將全部的數異或起來,就能夠獲得惟一的那個數。
1 #include<stdio.h> 2 int main() 3 { 4 int a[7]={1,2,2,1,3,4,3}; 5 int ans=0; 6 for(int i=0;i<7;i++){ 7 ans^=a[i]; 8 } 9 printf("%d\n",ans); 10 }
3.2.應用二 數組中,只有一個數出現一次,剩下都出現三次,找出出現一次的。(仍是很矇蔽)
問題
Given [1,1,2,3,3,3,2,2,4,1] return 4
解題思路
由於數是出現三次的,也就是說,對於每個二進制位,若是隻出現一次的數在該二進制位爲1,那麼這個二進制位在所有數字中出現次數沒法被3整除。
模3運算只有三種狀態:00,01,10,所以咱們可使用兩個位來表示當前位%3,對於每一位,咱們讓Two,One表示當前位的狀態,B表示輸入數字的對應位,Two+和One+表示輸出狀態。
0 0 0 0 0 0 0 1 0 1 0 1 0 0 1 0 1 1 1 0 1 0 0 1 0 1 0 1 0 0 One+ = (One ^ B) & (~Two) Two+ = (~One+) & (Two ^ B)
1 #include<stdio.h> 2 3 void findNum(int *a,int n) 4 { 5 int ans=0; 6 int bits[32]={0}; 7 for(int i=0;i<n;i++){ 8 for(int j=0;j<32;j++){ 9 bits[j]+=((a[i]>>j)&1); 10 } 11 } 12 for(int i=0;i<32;i++){ 13 if(bits[i]%3==1) ans+=1<<i; 14 } 15 printf("%d\n",ans); 16 } 17 int main() 18 { 19 int a[10]={1,1,2,3,3,3,2,2,4,1}; 20 findNum(a,10); 21 }
3.3.應用三 數組中,只有兩個數出現一次,剩下都出現兩次,找出出現一次的
問題
Given [1,2,2,3,4,4,5,3] return 1 and 5
解題思路
有了第一題的基本的思路,咱們不妨假設出現一個的兩個元素是x,y,那麼最終全部的元素異或的結果就是res = x^y。而且res!=0,那麼咱們能夠找出res二進制表示中的某一位是1,那麼這一位1對於這兩個數x,y只有一個數的該位置是1。對於原來的數組,咱們能夠根據這個位置是否是1就能夠將數組分紅兩個部分。求出x,y其中一個,咱們就能求出兩個了。
1 #include<stdio.h> 2 3 void findNum(int *a,int n) 4 { 5 int ans=0; 6 int pos=0; 7 int x=0,y=0; 8 for(int i=0;i<n;i++) 9 ans^=a[i]; 10 int tmp=ans; 11 while((tmp&1)==0){ 12 //終止條件是二進制tmp最低位是1 13 pos++; 14 tmp>>=1; 15 } 16 for(int i=0;i<n;i++){ 17 if((a[i]>>pos)&1){//取出第pos位的值 18 x^=a[i]; 19 } 20 } 21 y=x^ans; 22 if(x>y) swap(x,y);//從大到小輸出x,y 23 printf("%d %d\n",x,y); 24 } 25 int main() 26 { 27 int a[8]={1,2,2,3,4,4,5,3}; 28 findNum(a,8); 29 }
2019-05-07 12:05:42 編程小菜鳥自我檢討,大佬勿噴,謝謝!!!