有趣的二進制—高效位運算

上一篇《有趣的二進制》咱們講到二進制的一些基礎知識,但沒有講到位運算,有同窗大呼不過癮,那這一篇主要講解下位運算的運用,仍是從一個例子開始,但願對你們有啓發。記得後面例子應用請自行mark,幫助很大。html

數獨

數獨是介紹位運算的好例子,運用位運算和不運用效率差異仍是挺大的。咱們先看數獨需求:java

一、當前數字所在數字均含1-9,不重複git

二、當前數字所在數字均含1-9,不重複算法

三、當前數字所在(即3x3的大格)數字均含1-9,不重複(宮,以下圖每一個粗線內是一個宮)數組

學習

常規算法

如果咱們採用常規方式的,每填寫一個數字,須要檢查當前行、列,宮中其餘位置是否有重複數字,極端狀況下須要循環27(3*9)次來進行檢查,咱們看下常規算法下check優化

int check(int sp) {
	   // 檢查同行、列、九宮格有沒有相同的數字,如有傳回 1
	   int fg= 0 ;
	   if(!fg) fg= check1(sp, startH[sp], addH) ;   // 檢查同列有沒有相同的數字
	   if(!fg) fg= check1(sp, startV[sp], addV) ;   // 檢查同行有沒有相同的數字
	   if(!fg) fg= check1(sp, startB[sp], addB) ;   // 檢查同九宮格有沒有相同的數字
	   return(fg) ;
	}

	int check1(int sp, int start, int *addnum) {
	   // 檢查指定的行、列、九宮格有沒有相同的數字,如有傳回 1
	   int fg= 0, i, sp1  ;
	   //萬惡的for循環
	   for(i=0; i<9; i++) {
	      sp1= start+ addnum[i] ;
	      if(sp!=sp1 && sudoku[sp]==sudoku[sp1]) fg++ ;
	   }
	   return(fg) ;
	}

這個效率是否很嚇人,每次填寫一個就須要check27次,有木有check一次的算法?固然有了,採用位運算,一次搞定。來咱們看下位運算的思路:google

位運算

咱們看上圖所示,單個行(或者列、宮)數據,都是有1-9共9個數字,咱們統稱爲九宮數字。如果咱們採用二進制,以九宮數字充當二進制數據的位座標,採用9位的二進制就能夠與之一一對應,位上有數據,標識爲1,無數據標識爲0,如此一個正數就能解決一行九宮數據狀態,無需需存一個數組spa

好比 看圖中深紅色部分,當前九宮數據中已經有1和3,那麼二進制右起第一位和第三位標識爲1,一個數字5就能夠存下當前行(或者列、宮)數組狀態了,如若數字爲511代表,全部的九宮數字都用完了,如圖第一行。.net

check一個數字是否已經被佔用了,能夠採起位運算來獲取二進制的右數第k位來查看是不是1,如果1,代表指定數字已經被佔用了。咱們看下具體check算法:

// sp 是當前位置索引,indexV 行索引,indexH 列索引,indexB九宮格索引
	int check(int sp,int indexV,int indexH,int indexB) {
	   // 檢查同行、列、九宮格沒有用到的數字,若已經用過返回 1
		int status = statusV[indexV]|statusH[indexH]|statusB[indexB];
		//9個數字都被用了
		if (status>=STATUS_MAX_VALUE)
		{
			return 1;
		}
		int number=sudoku[sp];
		//取右數第k位,如果1代表這個值已經存在了
		return status>>(number-1)&1;
	}
// 行、列、宮二進制數據指定位置標記爲1
	int markStatus(int indexV,int indexH,int indexB,int number){
		if (number<1)
		{
			return 0;
		}
		//把右數第k(從1計數)位變成1 
   	  	statusV[indexV]|=(1<<(number-1));
    	statusH[indexH]|=(1<<(number-1));
    	statusB[indexB]|=(1<<(number-1));
	}

咱們以如下圖例位置舉例,如何得到當前位置能夠填取的數字

能夠看到2個位運算就解決了檢查可用數字的操做了,而以前常規算法,須要用27次查找才能夠獲取到。固然了這個算法還能夠優化,好比採用啓發式DFS,搜索可用數字,速度更快,感興趣可點擊這裏

常規算法和位運算算法C語言代碼,我已經上傳碼雲了,想了解的點擊下面連接,自行去查看去。(常規算法google的)

地址: 常規算法數獨位運算版本數獨

 基礎

位操做符

符號 含義 規則
兩個位都爲1時,結果爲1
| 有一個位爲1時,結果爲1
^ 異或 0和1異或0都不變,異或1則取反
~ 取反 0和1所有取反
<< 左移 位所有左移若干位,高位丟棄,低位補0
>> 算術右移 位所有右移若干位,,高位補k個最高有效位的值
>> 邏輯右移 位所有右移若干位,高位補0

注意:

一、位運算只可運用於整數,對於float和double不行。

二、另外邏輯右移符號各類語言不太同,好比java是>>>。

三、位操做符的運算優先級比較低,儘可能使用括號來確保運算順序。好比1&i+1,會先執行i+1再執行&。

 

應用實例

很棒的應用實例,你能夠mark一下,方便之後對照使用。

一、混合體

位運算實例

位運算 功能 示例
x >> 1 去掉最後一位 101101->10110
x << 1 在最後加一個0 101101->1011010
x << 1 | 1 在最後加一個1 101101->1011011
x | 1 把最後一位變成1 101100->101101
x & -2 把最後一位變成0 101101->101100
x ^ 1 最後一位取反 101101->101100
x | (1 << (k-1)) 把右數第k位變成1 101001->101101,k=3
x & ~ (1 << (k-1)) 把右數第k位變成0 101101->101001,k=3
x ^(1 <<(k-1)) 右數第k位取反 101001->101101,k=3
 x & 7 取末三位 1101101->101
x & (1 << k-1) 取末k位 1101101->1101,k=5
x >> (k-1) & 1 取右數第k位 1101101->1,k=4
x | ((1 << k)-1) 把末k位變成1 101001->101111,k=4
x ^ (1 << k-1) 末k位取反 101001->100110,k=4
x & (x+1) 把右邊連續的1變成0 100101111->100100000
x | (x+1) 把右起第一個0變成1 100101111->100111111
x | (x-1) 把右邊連續的0變成1 11011000->11011111
(x ^ (x+1)) >> 1 取右邊連續的1 100101111->1111
x & -x 去掉右起第一個1的左邊 100101000->1000
x&0x7F 取末7位 100101000->101000
x& ~0x7F 是否小於127 001111111 & ~0x7F->0
x & 1 判斷奇偶 00000111&1->1

二、交換兩數

int swap(int a, int b)  
{  
    if (a != b)  
    {  
        a ^= b;  
        b ^= a;  
        a ^= b;  
    }  
}

 

三、求絕對值

int abs(int a)  
{  
    int i = a >> 31;  
    return ((a ^ i) - i);  
}

 

四、二分查找32位整數前導0個數

int nlz(unsigned x)
{
   int n;

   if (x == 0) return(32);
   n = 1;
   if ((x >> 16) == 0) {n = n +16; x = x <<16;}
   if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
   if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
   if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
   n = n - (x >> 31);
   return n;
}

五、二進制逆序

int reverse_order(int n){

  n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1);
  n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2);
  n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4);
  n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8);
  n = ((n & 0xFFFF0000) >> 16) | ((n & 0x0000FFFF) << 16);

  return n;
}

六、 二進制中1的個數

unsigned int BitCount_e(unsigned int value) {
        unsigned int count = 0;
        // 解釋下下面這句話代碼,這句話求得兩兩相加的結果,例如 11 01 00 10
        // 11 01 00 10 = 01 01 00 00 + 10 00 00 10,即由奇數位和偶數位相加而成
        // 記 value = 11 01 00 10,high_v = 01 01 00 00, low_v = 10 00 00 10
        // 則 value = high_v + low_v,high_v 右移一位得 high_v_1,
        // 即 high_v_1 = high_v >> 1 = high_v / 2
        // 此時 value 能夠表示爲 value = high_v_1 + high_v_1 + low_v,
        // 可見 咱們須要 high_v + low_v 的和即等於 value - high_v_1
        // 寫簡單點就是 value = value & 0x55555555 + (value >> 1) & 0x55555555;
        value = value - ((value >> 1) & 0x55555555);

        // 以後的就好理解了
        value = (value & 0x33333333) + ((value >> 2) & 0x33333333);
        value = (value & 0x0f0f0f0f) + ((value >> 4) & 0x0f0f0f0f);
        value = (value & 0x00ff00ff) + ((value >> 4) & 0x00ff00ff);
        value = (value & 0x0000ffff) + ((value >> 8) & 0x0000ffff);
        return value;

        // 另外一種寫法,原理同樣,緣由在最後一種解法中有提到
        //value = (value & 0x55555555) + (value >> 1) & 0x55555555;
        //value = (value & 0x33333333) + ((value >> 2) & 0x33333333);
        //value = (value & 0x0f0f0f0f) + ((value >> 4) & 0x0f0f0f0f);
        //value = value + (value >> 8);
        //value = value + (value >> 16);
        //return (value & 0x0000003f);
    }

 

-----------------------end-------------------------

大碼候,關注我的成長和技術學習,期待用本身的一點點改變,可以給予你一些啓發

掃描關注更多。

相關文章
相關標籤/搜索