“異或”運算是C語言中一種比較冷僻的運算,除了一些特定領域的問題(如加密、圖像處理等),較少有恰當的應用場合。以至於大多數C語言書在講到異或這個運算時,一般都干巴巴的很生硬。
日前,看到 人人校招筆試題 中的對某問題的求解,發現異或在某些特定場合有非常奇妙的、恰如其分的甚至可說是舍我其誰的應用。
人人校招筆試題 中的問題是這樣的:
給定一個有序數組a,長度為len,和一個數x,判斷A數組里面是否存在兩個數,他們的和為x,bool judge(int *a, int len, int x),存在返回true,不存在則返回false。
這個問題並不太難,除了博主給出了代碼,另有網友給出了另外兩種代碼。
代碼1

#include <stdio.h> #include <stdlib.h> #include <string.h> int Judge(int *a, int len, int x) { int Ascending = 0;//為1表示升序,否則降序 Ascending = a[1] > a[0] ? 1 : 0; int *CopyA = (int *)malloc(sizeof(int) * len); memset(CopyA, 0, sizeof(int) * len); //構建另一數組 int icount = 0; for(icount = 0; icount < len; icount++) { CopyA[icount] = x - a[icount]; } //比較兩個指針移動的值,這里用索引代替指針 int i = len - 1, j = 0; while(i >= 0 && j < len) { if(a[i] > CopyA[j]) { switch(Ascending) { case 0: j++; break;//降序 case 1: i--; break;//升序 default:break; } } else if(a[i] == CopyA[j]) { return 1; } else { switch(Ascending) { case 0: i--; break;//降序 case 1: j++; break;//升序 default:break; } } } return 0; } int main() { int a[] = {59,41,21,10,5}; int len = 5; int x = 190; switch(Judge(a, len, x)) { case 0: printf("%d isn't exist!", x);break; case 1: printf("%d is exist!", x);break; default : break; } return 0; }
代碼2

bool judge(int *a, int len, int x) { int begin = 0; int end = len - 1; int order = 0; /*0-升序 1-降序*/ /*判斷升序還是降序*/ if(a[0] < a[len - 1]) { order = 0; } else { order = 1; } while(begin < end) { if(a[begin] + a[end] > x) { if(order == 0) --end; else ++begin; } else if(a[begin] + a[end] == x) { return true; } else { if(order == 0) ++begin; else --end; } } return false; }
代碼3

bool judge(int *a, int len, int x) { int begin = 0; int end = len - 1; int order = 0; /*0-升序 1-降序*/ /*判斷升序還是降序*/ if(a[0] < a[len - 1]) order = 0; else order = 1; if(order == 0) { while(begin < end) { if(a[begin] + a[end] > x) --end; else if(a[begin] + a[end] == x) return true; else ++begin; } } else { while(begin < end) { if(a[begin] + a[end] > x) ++begin; else if(a[begin] + a[end] == x) return true; else --end; } } return false; }
在這三種寫法中,思路是一致的,且都有一個同樣的毛病,就是啰嗦重復。
代碼1中:
switch(Ascending) { case 0: j++; break;//降序 case 1: i--; break;//升序 default:break; }
和
switch(Ascending) { case 0: i--; break;//降序 case 1: j++; break;//升序 default:break; }
幾乎雷同的代碼寫了兩次。除此之外,
Ascending = a[1] > a[0] ? 1 : 0;
是錯誤的;
“構建另一數組”是多余的。
memset(CopyA, 0, sizeof(int) * len);
很傻,不知所雲。
代碼2中
if(order == 0) --end; else ++begin;
和
if(order == 0) ++begin; else --end;
也是幾乎同樣的代碼寫了兩次。此外
if(a[0] < a[len - 1]) { order = 0; } else { order = 1; }
也屬於啰嗦重復代碼。因為前面既然已經
int order = 0; /*0-升序 1-降序*/
這里就完全沒必要再寫if-else語句,只需要寫
if(a[0] > a[len - 1]) { order = 1; }
就可以了。甚至連這個if語句也不需要寫,只要在聲明order變量時
int order = a[0] > a[len - 1]; /*0-升序 1-降序*/
就可以輕松完成同樣的功能。
代碼3中:
if(order == 0) { while(begin < end) { if(a[begin] + a[end] > x) --end; else if(a[begin] + a[end] == x) return true; else ++begin; } } else { while(begin < end) { if(a[begin] + a[end] > x) ++begin; else if(a[begin] + a[end] == x) return true; else --end; } }
啰嗦重復現象更為嚴重。代碼3寫得顯然比代碼2還要差得多。
造成這種重復現象的原因是,問題給出的條件是“有序”數組,但沒說是升序還是降序。
前面幾段代碼的算法都是考察數組兩端元素和是否等於指定的X,如和大於X,則拋棄大端元素,形成一個新數組;如和小於X,則拋棄小端元素形成一個新數組。形成新數組后,重復前面步驟繼續考察,直到數組兩端元素和等於X或數組中元素不足2個。
由於題目沒指定升序還是降序,所以一共有下面4種情況:
升序(0) | 升序(0) | 降序(1) | 降序(1) |
和>X(1) | 和<X(0) | 和>X(1) | 和<X(0) |
拋棄最右元素(1) | 拋棄最左元素(0) | 拋棄最左元素(0) | 拋棄最右元素(1) |
認真觀察一下不難發現,如果升序用0表示,降序用1表示;和>X 用1表示,和<X 用0表示;拋棄最右元素用1表示,拋棄最左元素用0表示的話,很顯然上面表中第3行的值與前兩行的值的異或運算的結果。由此可以簡單地給出如下代碼:
#include <stdio.h> #include <stdbool.h> #define X 31 bool judge(int [], size_t , int ); int main( void ) { int test[]= { 5,10,21,41,59 } ;//{ 59,41,21,10,5 }; printf ( "%d%s存在!\n", X , judge( test , sizeof test/sizeof *test, X ) ? "" :"不" ); return 0; } bool judge( int a[] , size_t n , int x ) { if ( n < 2u ) return false; if ( x == a[0] + a[n-1] ) return true; if ( (x > a[0] + a[n-1]) ^ ( a[0] > a[n-1]) ) return judge( a + 1, n - 1 , x ) ; return judge( a , n - 1 , x ); }
前面是用遞歸寫的,不用遞歸也不難實現:
bool judge( int a[] , size_t n , int x ) { while ( n > 1u ) { if ( x == a[0] + a[n-1] ) return true ; if ( (x > a[0] + a[n-1]) ^ ( a[0] > a[n-1]) ) a ++ ; n -- ; } return false ; }