二分查找及其变种
二分查找
普通二分查找
首先说下普通二分查找的思路
普通二分查找是在一个没有重复的排序数组中,找到目标值
思路就是先从中间找,如果中间值大于目标值,说明目标值在左半区
如果中间值小于目标值,说明目标值在右边,
当中间值等于目标值,返回他的下标,
// 这里的数组默认都不为空
// 给定一个有序数组和一个关键字,找到该值在数组中的下标,否则返回-1
int binary_search(vector<int>vec, int target) { int left = 0, right = vec.size()-1; while(left <= right) { // int mid = (left + right) >> 1; // 等价于mid = (left+right) 防止溢出 int mid = left + (right-left)/2; // 防止溢出 if (vec[mid] == target) return mid; else if (vec[mid] > target) // 在左半区 right = mid - 1; else left = mid + 1; //在右边区 } return -1; // 不存在 }
一般人可能直接这样写:int mid = (left + right) / 2,不能说错误,但存在问题:如果 left 和 right 比较大的话,两者之和就有可能会溢出。
改进的方法是将 mid 的计算方式写成 left + (right-left)/2。
更进一步,如果要将性能优化到极致的话,我们可以将这里的除以 2 操作转化成位运算 left + (right-left)>>1。
因为相比除法运算来说,计算机处理位运算要快得多。
二分查找变种写法
当出现的目标值有重复的怎么找
变种一:查找第一次出现的目标值
我们对数组data进行二分,如果数组中间的数字小于k,说明k应该出现在中间位置的右边;
如果数组中间的数字大于k,说明k应该出现在中间位置的左边;
如果数组中间的数字等于k,并且中间位置的前一个数字不等于k,
说明这个中间数字就是数字k出现的第一个位置。否则在左边找
int binary_search(vector<int>vec, int target) { int left = 0, right = vec.size()-1; while(left <= right) { int mid = left + (right-left)/2; if (vec[mid] > target) right = mid -1; else if (vec[mid] < target ) left = mid + 1; else { // 当目标值等于给定值时,如果mid是第一个元素或者前面没有目标值,就是要找的 if(mid == 0 || vec[mid - 1] != target) return mid; else right = mid - 1; //注意这里 } } return -1; }
变种二:
找出最后一个出现的目标数,思路和一类似
//找最后一个出现的,同上面类似,只要修改相等的 int binary_search(vector<int>vec, int target) { int left = 0, right = vec.size()-1; while(left <= right) { int mid = left + (right-left)/2; if (vec[mid] > target) right = mid-1; else if (vec[mid] < target) left = mid + 1; else { if (mid == vec.size()-1 || vec[mid+1] != target) return mid; else left = mid + 1; } } return -1; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <vector> using namespace std; int binary_search1(vector<int>vec, int target) { int left = 0, right = vec.size()-1; while(left <= right) { // int mid = (left + right) >> 1; // 等价于mid = (left+right) 防止溢出 int mid = left + (right-left)/2; // 防止溢出 if (vec[mid] == target) return mid; else if (vec[mid] > target) // 在左半区 right = mid - 1; else left = mid + 1; //在右边区 } return -1; // 不存在 } // 查找重复数组中第一个出现给定值 /* 思路, 和普通的二分改变的是当等于给定值需要修改 我们对数组data进行二分,如果数组中间的数字小于k,说明k应该出现在中间位置的右边; 如果数组中间的数字大于k,说明k应该出现在中间位置的左边; 如果数组中间的数字等于k,并且中间位置的前一个数字不等于k, 说明这个中间数字就是数字k出现的第一个位置。 */ int binary_search2(vector<int>vec, int target) { int left = 0, right = vec.size()-1; while(left <= right) { int mid = left + (right-left)/2; if (vec[mid] > target) right = mid -1; else if (vec[mid] < target ) left = mid + 1; else { // 当目标值等于给定值时,如果mid是第一个元素或者前面没有目标值,就是要找的 if(mid == 0 || vec[mid - 1] != target) return mid; else right = mid - 1; } } return -1; } //找最后一个出现的,同上面类似,只要修改相等的 int binary_search3(vector<int>vec, int target) { int left = 0, right = vec.size()-1; while(left <= right) { int mid = left + (right-left)/2; if (vec[mid] > target) right = mid-1; else if (vec[mid] < target) left = mid + 1; else { if (mid == vec.size()-1 || vec[mid+1] != target) return mid; else left = mid + 1; } } return -1; } int main() { // 连续不重复的数组,普通的二分查找 vector<int> vec1 = {1,2,3,4,5,6}; cout << binary_search1(vec1, 4) << endl; // 含有重复的,找出第一个等于给定值的下标 vector<int> vec2 = {1,2,2,2,2,6}; cout <<"第一次出现2的下标" <<binary_search2(vec2, 2) << endl; // 含有重复的,找出最后一个等于给定值的下标 cout <<"最后一次出现2的下标" <<binary_search3(vec2, 2) << endl; return 0; }
普通二分也可以递归实现
// 二分查找的递归实现 int bSearch1(vector<int>a, int n, int value) { return bSearchInternally(a, 0, n - 1, value); } int bSearchInternally(vector<int>a, int low, int high, int value) { if (low > high) return -1; int mid = low + ((high - low) >> 1); if (a[mid] == value) { return mid; } else if (a[mid] < value) { return bSearchInternally(a, mid+1, high, value); } else { return bSearchInternally(a, low, mid-1, value); } }