二进制-高效位运算
數(shù)獨
數(shù)獨是介紹位運算的好例子,運用位運算和不運用效率差別還是挺大的。我們先看數(shù)獨需求:
1、當(dāng)前數(shù)字所在行數(shù)字均含1-9,不重復(fù)
2、當(dāng)前數(shù)字所在列數(shù)字均含1-9,不重復(fù)
3、當(dāng)前數(shù)字所在宮(即3x3的大格)數(shù)字均含1-9,不重復(fù)(宮,如下圖每個粗線內(nèi)是一個宮)
、
常規(guī)算法
若是我們采用常規(guī)方式的,每填寫一個數(shù)字,需要檢查當(dāng)前行、列,宮中其他位置是否有重復(fù)數(shù)字,極端情況下需要循環(huán)27(3*9)次來進(jìn)行檢查,我們看下常規(guī)算法下check
int check(int sp) { // 檢查同行、列、九宮格有沒有相同的數(shù)字,若有傳回 1 int fg= 0 ; if(!fg) fg= check1(sp, startH[sp], addH) ; // 檢查同列有沒有相同的數(shù)字 if(!fg) fg= check1(sp, startV[sp], addV) ; // 檢查同行有沒有相同的數(shù)字 if(!fg) fg= check1(sp, startB[sp], addB) ; // 檢查同九宮格有沒有相同的數(shù)字 return(fg) ; } int check1(int sp, int start, int *addnum) { // 檢查指定的行、列、九宮格有沒有相同的數(shù)字,若有傳回 1 int fg= 0, i, sp1 ; //萬惡的for循環(huán) for(i=0; i<9; i++) { sp1= start+ addnum[i] ; if(sp!=sp1 && sudoku[sp]==sudoku[sp1]) fg++ ; } return(fg) ; }這個效率是否很嚇人,每次填寫一個就需要check27次,有木有check一次的算法?當(dāng)然有了,采用位運算,一次搞定。來我們看下位運算的思路:
位運算
我們看上圖所示,單個行(或者列、宮)數(shù)據(jù),都是有1-9共9個數(shù)字,我們統(tǒng)稱為九宮數(shù)字。若是我們采用二進(jìn)制,以九宮數(shù)字充當(dāng)二進(jìn)制數(shù)據(jù)的位坐標(biāo),采用9位的二進(jìn)制就可以與之一一對應(yīng),位上有數(shù)據(jù),標(biāo)識為1,無數(shù)據(jù)標(biāo)識為0,如此一個正數(shù)就能解決一行九宮數(shù)據(jù)狀態(tài),無需需存一個數(shù)組。
比如 看圖中深紅色部分,當(dāng)前九宮數(shù)據(jù)中已經(jīng)有1和3,那么二進(jìn)制右起第一位和第三位標(biāo)識為1,一個數(shù)字5就可以存下當(dāng)前行(或者列、宮)數(shù)組狀態(tài)了,如若數(shù)字為511表明,所有的九宮數(shù)字都用完了,如圖第一行。
check一個數(shù)字是否已經(jīng)被占用了,可以采取位運算來獲取二進(jìn)制的右數(shù)第k位來查看是否是1,若是1,表明指定數(shù)字已經(jīng)被占用了。我們看下具體check算法:
// sp 是當(dāng)前位置索引,indexV 行索引,indexH 列索引,indexB九宮格索引int check(int sp,int indexV,int indexH,int indexB) { // 檢查同行、列、九宮格沒有用到的數(shù)字,若已經(jīng)用過返回 1 int status = statusV[indexV]|statusH[indexH]|statusB[indexB]; //9個數(shù)字都被用了 if (status>=STATUS_MAX_VALUE) { return 1; } int number=sudoku[sp]; //取右數(shù)第k位,若是1表明這個值已經(jīng)存在了 return status>>(number-1)&1; } // 行、列、宮二進(jìn)制數(shù)據(jù)指定位置標(biāo)記為1int markStatus(int indexV,int indexH,int indexB,int number){ if (number<1) { return 0; } //把右數(shù)第k(從1計數(shù))位變成1 statusV[indexV]|=(1<<(number-1)); statusH[indexH]|=(1<<(number-1)); statusB[indexB]|=(1<<(number-1)); }我們以以下圖例位置舉例,如何獲得當(dāng)前位置可以填取的數(shù)字
可以看到2個位運算就解決了檢查可用數(shù)字的操作了,而之前常規(guī)算法,需要用27次查找才可以獲取到。當(dāng)然了這個算法還可以優(yōu)化,比如采用啟發(fā)式DFS,搜索可用數(shù)字,速度更快,感興趣可點擊這里。
常規(guī)算法和位運算算法C語言代碼,我已經(jīng)上傳碼云了,想了解的點擊下面鏈接,自行去查看去。(常規(guī)算法google的)
地址:?常規(guī)算法數(shù)獨,位運算版本數(shù)獨
?基礎(chǔ)
位操作符
| &? | 與 | 兩個位都為1時,結(jié)果為1 |
| | | 或 | 有一個位為1時,結(jié)果為1 |
| ^ | 異或 | 0和1異或0都不變,異或1則取反 |
| ~ | 取反 | 0和1全部取反 |
| << | 左移 | 位全部左移若干位,高位丟棄,低位補0 |
| >> | 算術(shù)右移 | 位全部右移若干位,,高位補k個最高有效位的值 |
| >> | 邏輯右移 | 位全部右移若干位,高位補0 |
注意:
1、位運算只可運用于整數(shù),對于float和double不行。
2、另外邏輯右移符號各種語言不太同,比如java是>>>。
3、位操作符的運算優(yōu)先級比較低,盡量使用括號來確保運算順序。比如1&i+1,會先執(zhí)行i+1再執(zhí)行&。
?
應(yīng)用實例
很棒的應(yīng)用實例,你可以mark一下,方便以后對照使用。
1、混合體
位運算實例
| 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)) | 把右數(shù)第k位變成1 | 101001->101101,k=3 |
| x & ~ (1 << (k-1)) | 把右數(shù)第k位變成0 | 101101->101001,k=3 |
| x ^(1 <<(k-1)) | 右數(shù)第k位取反 | 101001->101101,k=3 |
| ?x & 7 | 取末三位 | 1101101->101 |
| x & (1 << k-1) | 取末k位 | 1101101->1101,k=5 |
| x >> (k-1) & 1 | 取右數(shù)第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) | 把右邊連續(xù)的1變成0 | 100101111->100100000 |
| x | (x+1) | 把右起第一個0變成1 | 100101111->100111111 |
| x | (x-1) | 把右邊連續(xù)的0變成1 | 11011000->11011111 |
| (x ^ (x+1)) >> 1 | 取右邊連續(xù)的1 | 100101111->1111 |
| x & -x | 去掉右起第一個1的左邊 | 100101000->1000 |
| x&0x7F | 取末7位 | 100101000->101000 |
| x& ~0x7F | 是否小于127 | 001111111 &?~0x7F->0 |
| x &?1 | 判斷奇偶 | 00000111&1->1 |
2、交換兩數(shù)
int swap(int a, int b) { if (a != b) { a ^= b; b ^= a; a ^= b; } }?
3、求絕對值
int abs(int a) { int i = a >> 31; return ((a ^ i) - i); }?
4、二分查找32位整數(shù)前導(dǎo)0個數(shù)
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; }5、二進(jì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; }6、?二進(jìn)制中1的個數(shù)
unsigned int BitCount_e(unsigned int value) { unsigned int count = 0; // 解釋下下面這句話代碼,這句話求得兩兩相加的結(jié)果,例如 11 01 00 10 // 11 01 00 10 = 01 01 00 00 + 10 00 00 10,即由奇數(shù)位和偶數(shù)位相加而成 // 記 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); }?
轉(zhuǎn)載于:https://www.cnblogs.com/hwcs/p/7196918.html
總結(jié)
- 上一篇: Android开发必用工具及其进阶途径
- 下一篇: 选择行_外出旅游你会选择自由行还是跟团游