最近在做二分搜索的题目,可是正如在编程珠玑里面所说的一样,正确的二分搜索只有10%的程序员才能够写出来。
去google搜索了一下:如何写出正确的二分搜索,开头就看见了Hacker News上面的文章:
https://news.ycombinator.com/item?id=1277459
于是花了几个小时研究了一下二分搜索。
1:当然最简单的情形是从给定范围内找到指定的数字,下面我所说的各种情况范围两边都是闭区间的,即[low , high],(范围包括low 和 high)
当你要指定其余查找范围的时候可以很容易的转化为两端闭区间,
如(low, high) -> [low + 1, high - 1] (low, high] -> [low + 1, high] [low, high) -> [low, high + 1]
那么要查找该范围内的,首先[low, high] (当low == high的时候表示只有一个点的区间,low > high的时候表示空区间)
所以我们的循环条件就应该如此写: while (low <= high) (因为是闭区间表示法,不能够写成low < high),当只有一个点的区间查找的时候显然是错误的。
那么查找一个数的二分查找条件就很容易写
当 array[mid] == number_to_find的时候直接返回。
当array[mid] < number_to_find的时候,查找区间就应该更新为low = mid + 1;
当array[mid] > number_to_find的时候,查找区间就应该更新为high = mid - 1;
那么一个简单正确的查找一个数的二分搜索就出来了:
int binarySearchOneNumber(int number_to_find, int low, int high, int *array) { int mid; while (low <= high) { mid = (low + high) / 2; if (array[mid] == number_to_find) { return array[mid]; } else if (array[mid] > number_to_find) { high = mid - 1; } else { low = mid + 1; } } return -1; }
下标最小的:
循环条件还是不变,因为我们采取的是闭区间的形式为while(low <= high)
那么当找到一个数满足条件的时候,如array[mid] == number_to_find的时候不能够直接返回。因为有可能存在比mid下标还小的,并且元素值等于number_to_find的元素。
所以我们必须更新high。如果high更新为mid的话,即high = mid;会发生死循环。为什么呢?
(1)假设不再存在满足条件的元素了,那么low一定会逐渐向上更新,更新到和high一样的大小,那么此时就会发生死循环。
(2)如果还存在的话,会被继续划分区间,直到划分成[low, high]里面不存在满足条件的元素,根据(1)会发生死循环。
所以我们应该更新high = mid - 1;
low的更新是毫无疑问的low = mid + 1;
那么我们最后应该返回的是什么呢?
假设我们找到了array[mid] == number_to_find,此时更新high = mid - 1,如果mid就是最小元素的话,那么下一次分的时候一定会逐渐的增加low的值,一直到high - 1,发现
array[high - 1]仍然比number_to_find小,low就会更新为high + 1 = mid,即找到的满足条件的下标。
如果还有满足条件元素的话,同理又会在一次找到一个mid,一直找到最小下标,然后再分为没有满足条件的区间,low一定会被更新为满足条件最小元素下标
所以我们最终返回low即可。
代码:
int binarySearchSmallestNumber(int number_to_find, int low, int high, int *array) { int mid; while (low <= high) { mid = (low + high) / 2; if (array[mid] >= number_to_find) { high = mid - 1; } else { low = mid + 1; } } return low; }
int binarySearchBiggestNumber(int number_to_find, int low, int high, int *array) { int mid; while (low <= high) { mid = (low + high) / 2; if (array[mid] <= number_to_find) { low = mid + 1; } else { high = mid - 1; } } return high; }
如果没有找到元素的话,那么找最小下标的二分就会返回一个下标k,此时如果将查找数插入K,并且将array[k], array[k + 1].... array[high]依次向后移动的话仍然是一个有序数组。
同理找最大下标的返回下标K,将array[k], array[k - 1].....array[0]向前移动的话仍然是一个有序数组。