二分查找算法,是在大学阶段学习的基础性算法。算法思想简单,但要写出一个正确的二分查找算法并不简单,从1946年提出,到1962年才完成一个正确的二分查找排序算法。二分查找排序算法的变种,也常常出现在各个IT公司的面试题中。本文总结资料尝试给出一个“正确”的二分查找算法,最后给出一个二分查找排序的变种问题。
1. 经典二分查找排序的算法:
1 int BinarySearch(int* array, int n, int x) 2 { 3 assert((array != NULL) && (0 <= n)); 4 5 int low = 0; 6 int high = n -1; 7 8 while (low <= high) 9 { 10 int mid = low + ((high - low) >> 1); 11 if (array[mid] == x) 12 { 13 return mid; 14 } 15 if (x > array[mid]) 16 { 17 low = mid + 1; 18 } 19 else 20 { 21 high = mid -1; 22 } 23 } 24 return -1; 25 }
可能需要关注细节问题:
1)循环条件
在第8行,循环的条件设置成 low <=high。如果设置成low<high,是存在问题的:当只有数组只有一个元素,并且该元素就是需要查找的元素时,算法返回错误的值-1,查找失败。
2)求中间元素
在第10行,求中间元素使用的代码:mid = low + ((high - low) >> 1)。一些书籍中给出的是:mid = (high + low) >> 1,如果high+low很大时,会造成溢出。这种情况出现的概率较小,但是为了程序适应比较恶劣条件,使用第10行代码求中间元素就显得有必要了。
(ps:上面的代码可能还存在带完善的地方,欢迎拍砖。。。)
2. 二分查找排序算法的变种
问题描述:已知一个有序的数组a[N],循环左移k位(k<N)后得到新的数组b[N],在这个新的数组中查找元素x。例如:数组 a[N] = {1, 3, 5, 7, 9, 10},循环左移2位后变成数组b[N]= {5, 7, 9, 10, 1, 3},在这个新的数组上查找某个元素x。
上题是我在面试中问道的问题,比较庆幸的是我在《剑指offer》一书中碰到了类似的一题,书中需要找出数组b[N]最小的最小元素,因此能比较迅速的解决了这个问题。解决问题的思路和《剑指offer》提供的思路一致。
解题思路:数组b[N]中存在着两个部分有序的子数组,b1:{5, 7, 9, 10}和b2:{1, 3},其中b1中的元素均大于b2中的元素。在使用二分查找算法时,中间元素mid必定落入b1或者b2某个递增序列中,具体解决步骤如下:
Init: low = 0, high = N-1, mid = b[low + (high - low) >> 1]
step1: 求出数组b的中间元素mid, 如果mid == x,则返回成功,否则进入步骤step2。
step2: if(a[low]<mid),mid落入b1数组,此时:
1) if (a[low]<x<mid), x在有序区间[low, mid),则将high = mid -1
2) 否则,x落入区间(mid, high],则将low = mid +1
else mid落入b2数组,此时:
1)if (mid< x< b[high]),x在有序区间(mid, high],则将low = mid + 1
2)否则, x落入区间[low, mid),则将hign = mid -1
step3: if(low <= hign) 继续步骤step1,否则返回查找失败
具体的代码如下:
1 int rotate_array_search(int a[], int n, int x) 2 { 3 int low = 0; 4 int high = n-1; 5 while(low <= high) 6 { 7 int mid = low + ((high - low) >> 1);
8 if(a[mid] == x) 9 return mid;
10 if(a[mid] >= a[low]) 11 { 12 if(x < a[mid] && x >= a[low]) 13 { 14 high = mid -1; 15 } 16 else 17 { 18 low = mid + 1; 19 } 20 } 21 else 22 { 23 if(x > a[mid] && x <= a[high]) 24 { 25 low = mid + 1; 26 }
else 27 { 28 high = mid -1; 29 } 30 } 31 } 32 return -1; // 查找失败,则返回-1 33 }