二分查找(binary search)
1 二分查找的思想
每次将待查找元素与区间中间元素进行比较,将查找区间缩减为之前的一半,直到找到待查找元素或查找区间大小为0。
2 实现及关键点
2.1 关键点
1)循环退出条件
循环退出条件为low <= high,其中low为查找区间的下边界、high为上边界,而不是low < high。如果条件为low < high,那么当查找区间大小为1即low = high时,循环退出就无法与最后一个数据元素进行比较。
2)区间中间索引mid的取值
一般来说mid = (low + high) / 2,但是当low + high超出其数据类型范围时会造成溢出,所以这样的mid取值是不稳妥的。mid = low + (high - low) / 2这样减少了溢出的可能,或者mid = low + ((high - low) >> 1)这样通过位运算使得计算更加快速。
3)区间上下边界的更新
更新应为low = mid + 1、high = mid - 1而不是low = mid、high = mid。因为如果是low = mid、high = mid的更新方式,那么当查找区间为1时而最后的数据元素又不等于查找元素,那么程序会进入死循环。
2.2 实现
1 /*查找升序数组中是否存在数据元素num,存在返回其在数组中的位置,不存在则返回-1*/ 2 int BinarySearch(int* pArrNum, int n, int num) { 3 int low = 0; //低索引 4 int high = n - 1; //高索引 5 int mid; //查找区间中间元素索引 6 7 while (low <= high) { 8 mid = low + ((high - low) >> 1); 9 if (num == pArrNum[mid]) return mid; 10 else if (num > pArrNum[mid]) low = mid + 1; 11 else high = mid - 1; 12 } 13 14 return -1; 15 }
2.3 复杂度
时间复杂度O(logn),空间复杂度O(1)。
3 适用场景
1)有序数组。二分查找算法依赖于按照下标随机访问元素,所以必须是数组;其次,二分查找针对的数据必须是有序的,如果无序要先对其进行排序。
2)数据量太小不适合二分查找。如果数据量太小,例如只有10个数据元素,那么顺序查找就足够了。但是如果两个数据元素的比较操作耗时较多时,例如长度较大的字符串比较,那么还是二分查找合适,因为二分查找比较次数较少。
3)数据量太大不适合二分查找。数组这种数据结构的对内存依赖较大,要求分配连续的内存空间,当数据量较大时很可能会导致内存分配失败。
4 二分查找的变式
1)查找第一个值等于给定值的元素;
2)查找最后一个值等于给定值的元素;
3)查找第一个大于等于给定值的元素;
4)查找最后一个小于等于给定值的元素;
变体的二分查找问题要注意一下几个细节:循环退出条件、区间上下界更新方法和返回值选择。写二分查找代码时不要过于追求完美、整洁的写法,代码易读、没有bug更重要。
1 /*查找第一个等于给定值的元素,存在返回其在数组中的位置,不存在则返回-1*/ 2 int SearchFirstElem(int* pArrNum, int n, int num) { 3 int low = 0; 4 int high = n - 1; 5 int mid; 6 int first = -1; //第一个等于给定值的元素下表 7 8 while (low <= high) { 9 mid = low + ((high - low) >> 1); 10 if (pArrNum[mid] == num) first = mid, high = mid - 1; 11 else if (pArrNum[mid] > num) high = mid - 1; 12 else low = mid + 1; 13 } 14 15 return first; 16 } 17 18 /*查找最后一个等于给定值的元素,存在返回其在数组中的位置,不存在则返回-1*/ 19 int SearchLastElem(int* pArrNum, int n, int num) { 20 int low = 0; 21 int high = n - 1; 22 int mid; 23 int last = -1; //最后一个等于给定值的元素下表 24 25 while (low <= high) { 26 mid = low + ((high - low) >> 1); 27 if (pArrNum[mid] == num) last = mid, low = mid + 1; 28 else if (pArrNum[mid] > num) high = mid - 1; 29 else low = mid + 1; 30 } 31 32 return last; 33 } 34 35 /*查找第一个大于等于给定值的元素,存在返回其在数组中的位置,不存在则返回-1*/ 36 int SearchFirstGigOrEqualElem(int* pArrNum, int n, int num) { 37 int low = 0; 38 int high = n - 1; 39 int mid; 40 int first = -1; //第一个大于等于给定值的元素下表 41 42 while (low <= high) { 43 mid = low + ((high - low) >> 1); 44 if (pArrNum[mid] >= num) first = mid, high = mid - 1; 45 else low = mid + 1; 46 } 47 48 return first; 49 } 50 51 /*查找最后一个大于等于给定值的元素,存在返回其在数组中的位置,不存在则返回-1*/ 52 int SearchLastLittleOrEqualElem(int* pArrNum, int n, int num) { 53 int low = 0; 54 int high = n - 1; 55 int mid; 56 int last = -1; //第一个等于给定值的元素下表 57 58 while (low <= high) { 59 mid = low + ((high - low) >> 1); 60 if (pArrNum[mid] <= num) last = mid, low = mid + 1; 61 else high = mid - 1; 62 } 63 64 return last; 65 }
该篇博客是自己的学习博客,水平有限,如果有哪里理解不对的地方,希望大家可以指正!