1. 程式人生 > >強大的工具--位運算

強大的工具--位運算

左移 返回 使用 include while 勿噴 5-0 表達式 csdn

近日來在看書的過程當中被這樣的一句話 假設hi和low是兩個整數,它們的值介於0到15之間,如果r是一個8位整數,且r的低四位與low各位上的數一致,而r的高4位與hi各位上的數一致,很自然會想到要這樣寫:

r = hi << (4 + low); 整的迷惑,說實話,位運算也學過,但使用最多的也就是在交換數值當中使用異或運算,而當我明白書中這句話裏表達式的含義時,深深被其中的精妙所折服,於是在網上搜集了關於位運算的一些使用技巧,記錄以下,以供後日參考(其中借鑒了csdn和百度上的一些問答,因為數量繁雜,記得不是太全,向這些博主表示衷心的感謝!):

首先還是從最基礎的說起,寫程序位運算是必要的嗎,以我的理解並不是,但是位運算正由於其本身能夠直接操作底層二進制數的特性,能提高近百分之60左右的運算效率,也是值得去學習和深入研究的,在學校的學習中位運算,課堂的知識講解的比較少,把c/c++中 位運算 的基礎知識先羅列一下

運算符 含義 功能
& 按位與 如果兩個相應的二進制位都為1,則該位的結果值為1;否則為0。
| 按位或 兩個相應的二進制位中只要有一個為1,該位的結果值為1。
按位異或 若參加運算的兩個二進制位同號則結果為0(假)異號則結果為1(真)
取反 ~是一個單目(元)運算符,用來對一個二進制數按位取反,即將0變1,將1變0。
<< 左移 左移運算符是用來將一個數的各二進制位全部左移N位,右補0。
>> 右移 表示將a的各二進制位右移N位,移到右端的低位被舍棄,對無符號數,高位補0。

 在基礎知識方面需要註意的是,在計算機中二進制數都是以補碼的形式存在的,方便機器進行運算,正數的原碼和補碼是相同的,而負數的補碼需要原碼進行取反加一,在位運算時需要多加註意

有位大神將位運算的使用總結成一句口訣:

清零取反要用與,某位置一可用或

若要取反和交換,輕輕松松用異或

接下來就是位運算的各種使用技巧:

(1) 按位與-- &

1 清零特定位 (mask中特定位置0,其它位為1,s=s&mask)

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 編程小菜鳥自我反省,大佬勿噴,謝謝!!!



強大的工具--位運算