巧用“异或”
“异或”运算是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 ; }