前言
的确,我一开始的时候也认为二分查找挺简单的,但是在我对二分查找进行总结的时候,发现虽然思路很简单,但是代码要写的正确就不容易了。
区间
需要注意的是
注意计算的区间是左闭右开区间[)还是左闭右闭区间[],两者的代码是不太一样的。
左闭右闭区间
如果说你使用的是左闭右闭区间:
int search3(int array[], int n, int target) { int left = 0; int right = n - 1; //比如说只有一个情况下 while (left <= right ) { //需要等于号,不然会进不来 int middle = left + (right - left) >> 1; if (target < array[middle]) { right = middle - 1; } else if (target > array[middle]) { left = middle + 1; } else { return middle; } } return -1;}
左闭右开区间
如果使用的是左闭右开区间:
// 左闭右开区间int search4(int array[], int n, int target) { int left = 0; int right = n; while (left < right) { int middle = left + (right - left) >> 1; if (target < array[middle]) { right = middle; } else if (target > array[middle]) { left = middle + 1; } else { return middle; } } return -1;}
lower_bound 和 upper_bound 和 binary_search 和 equal_range
我们可以发现,它们的代码是有区别的
左闭右闭区间,right = n - 1,left <= right,right = middle - 1
左闭右开区间,right = n,left < right,right = middle
为什么会这样呢?比如说n为1的时候,right此时为0,此时left也为0,需要等号才能进行while 循环。
还有需要注意的是,计算middle的时候,先计算减法是为了避免溢出。
STL的使用:
lower_bound:找到第一个 >= target的位置upper_bound:找到第一个 > target的位置
比如说给定的数组[1,2,2,2,3]
使用lower_bound返回的是第一个值为2的迭代器;使用upper_bound返回的是最后一个值为2的下一个,也就是值为3的迭代器;
使用lower_bound
- 如果mid < val,显然mid本身及其左边的元素都不可能是答案了,则把first置为 mid + 1。
- 如果mid >= val,答案可能是mid本身或者mid左边的元素,则把last置为mid。
使用upper_bound
- 如果mid <= val,显然mid其左边的元素都不可能是答案了,则把first置为 mid + 1。
- 如果mid > val,答案可能是mid右边的元素,所以把last设置成mid。
binary_search:查找某个元素是否出现
equal_range:查找某个元素出现的起止位置(终止位置为最后一次出现的位置加1)
实现
二分查找元素key的下标,如无return -1
有两个版本:
// 二分查找找数组下标int search(int array[], int low, int high, int target) { if (low > high) { return -1; } int mid = (low + high) / 2; if (array[mid] < target) { return search(array, low, mid, target); } else if (array[mid] > target) { return search(array, mid + 1, high, target); } else { return mid; }}// 迭代版本int search2(int array[], int low, int high, int target) { while (low <= high) { int mid = (low + high) / 2; if (array[mid] > target) { high = mid-1; } else if (array[mid] < target) { low = mid+1; } else { return mid; } } return -1;}
二分查找返回key(可能有重复)第一次出现的下标,如无return -1
// 求最小的i,使得a[i] = key,其实就是求有重复数值的数组中第一次出现key的下标// 重点在于不能要等于,如果(l <= r)这样的话,会导致陷入死循环,因为题目要求计算的是最小下标// 并且(a[m] < key),所以导致r=m,l=r,会退出不了// 循环退出条件:left == rightint binary_search_1(int a[], int n, int key) { int m, l = 0, r = n - 1;//闭区间[0, n - 1] while (l < r) { m = l + ((r - l) >> 1);//向下取整 cout << ",m " << m; if (a[m] < key) l = m + 1; else r = m; } if (a[r] == key) return r; return -1;}// 同上int binary_search_1_1(int a[], int n, int key) { int res = (int)(lower_bound(a, a + n, key) - a); return (res == n || a[res] != key) ? -1 : res;}
二分查找返回key(可能有重复)最后一次出现的下标,如无return -1
// 求最大的i,使得a[i] = key,其实就是求有重复数值的数组中最后一次出现key的下标// 循环退出条件:left == right || left == right - 1int binary_search_2(int a[], int n, int key) { int m, l = 0, r = n - 1;//闭区间[0, n - 1] while (l < r) { m = l + ((r + 1 - l) >> 1);//向上取整 cout << ",m " << m; if (a[m] <= key) l = m; else r = m - 1; } if (a[l] == key) return l; return -1;}// 同上int binary_search_2_2(int a[], int n, int key) { int res = (int)(upper_bound(a, a + n, key) - a); return (res == 0 || a[res - 1] != key) ? -1 : res;}
二分查找返回刚好小于key的元素下标,如无return -1
// 求最大的i,使得a[i] < key,如果key已经是最小的了,则返回-1// 其实就是求一个key前一个位置的下标int binary_search_4(int a[], int n, int key) { int m, l = 0, r = n - 1;//闭区间[0, n - 1] while (l < r) { m = l + ((r + 1 - l) >> 1);//向上取整 cout << ",m " << m; if (a[m] < key) l = m; else r = m - 1; } if (a[l] < key) return l; return -1;}// 同上int binary_search_4_4(int a[], int n, int key) { int res = (int)(lower_bound(a, a + n, key) - a); return (res == 0) ? -1 : res;}
二分查找返回刚好大于key的元素下标,如无return -1
// 求最小的i,使得a[i] > key,如果key是最大的了,则返回-1// 其实就是求最后一个key的下一个位置的下标// 需要(a[m] <= key),这样才能把相等的数值去除掉int binary_search_3(int a[], int n, int key){ int m, l = 0, r = n - 1;//闭区间[0, n - 1] while (l < r) { m = l + ((r - l) >> 1);//向下取整 cout << ",m " << m; if (a[m] <= key) l = m + 1; else r = m; } if (a[r] > key) return r; return -1;}// 同上int binary_search_3_3(int a[], int n, int key) { int res = (int)(upper_bound(a, a + n, key) - a); return (res == n) ? -1 : res;}
全部的代码如下:
#include using namespace std;// 二分查找找数组下标int search(int array[], int low, int high, int target) { if (low > high) { return -1; } int mid = (low + high) / 2; if (array[mid] < target) { return search(array, low, mid, target); } else if (array[mid] > target) { return search(array, mid + 1, high, target); } else { return mid; }}// 迭代版本int search2(int array[], int low, int high, int target) { while (low <= high) { int mid = (low + high) / 2; if (array[mid] > target) { high = mid-1; } else if (array[mid] < target) { low = mid+1; } else { return mid; } } return -1;}// 左闭右闭区间int search3(int array[], int n, int target) { int left = 0; int right = n - 1; //比如说只有一个情况下 while (left <= right ) { //需要等于号,不然会进不来 int middle = left + (right - left) >> 1; if (target < array[middle]) { right = middle - 1; } else if (target > array[middle]) { left = middle + 1; } else { return middle; } } return -1;}// 左闭右开区间int search4(int array[], int n, int target) { int left = 0; int right = n; while (left < right) { int middle = left + (right - left) >> 1; if (target < array[middle]) { right = middle; } else if (target > array[middle]) { left = middle + 1; } else { return middle; } } return -1;}// 寻找包含某个值的区间[begin,end]void search5(int array[], int left, int right, int target, int& begin, int& end) { if (left >= right) { return ; } int mid = right - (right - begin)/2; if (array[mid] == target) { if (mid < begin || begin == -1) { begin = mid; } if (mid > end) { end = mid; } search5(array, left, mid-1, target, begin, end); search5(array, mid+1, right, target, begin, end); } else if (array[mid] > target) { search5(array, left, mid-1, target, begin, end); } else { search5(array, mid+1, right, target, begin, end); }}// 求最小的i,使得a[i] = key,其实就是求有重复数值的数组中第一次出现key的下标// 重点在于不能要等于,如果(l <= r)这样的话,会导致陷入死循环,因为题目要求计算的是最小下标// 并且(a[m] < key),所以导致r=m,l=r,会退出不了// 循环退出条件:left == rightint binary_search_1(int a[], int n, int key) { int m, l = 0, r = n - 1;//闭区间[0, n - 1] while (l < r) { m = l + ((r - l) >> 1);//向下取整 cout << ",m " << m; if (a[m] < key) l = m + 1; else r = m; } if (a[r] == key) return r; return -1;}// 同上int binary_search_1_1(int a[], int n, int key) { int res = (int)(lower_bound(a, a + n, key) - a); return (res == n || a[res] != key) ? -1 : res;}// 求最大的i,使得a[i] = key,其实就是求有重复数值的数组中最后一次出现key的下标// 循环退出条件:left == right || left == right - 1int binary_search_2(int a[], int n, int key) { int m, l = 0, r = n - 1;//闭区间[0, n - 1] while (l < r) { m = l + ((r + 1 - l) >> 1);//向上取整 cout << ",m " << m; if (a[m] <= key) l = m; else r = m - 1; } if (a[l] == key) return l; return -1;}// 同上int binary_search_2_2(int a[], int n, int key) { int res = (int)(upper_bound(a, a + n, key) - a); return (res == 0 || a[res - 1] != key) ? -1 : res;}// 求最小的i,使得a[i] > key,如果key是最大的了,则返回-1// 其实就是求最后一个key的下一个位置的下标// 需要(a[m] <= key),这样才能把相等的数值去除掉int binary_search_3(int a[], int n, int key){ int m, l = 0, r = n - 1;//闭区间[0, n - 1] while (l < r) { m = l + ((r - l) >> 1);//向下取整 cout << ",m " << m; if (a[m] <= key) l = m + 1; else r = m; } if (a[r] > key) return r; return -1;}// 同上int binary_search_3_3(int a[], int n, int key) { int res = (int)(upper_bound(a, a + n, key) - a); return (res == n) ? -1 : res;}// 求最大的i,使得a[i] < key,如果key已经是最小的了,则返回-1// 其实就是求一个key前一个位置的下标int binary_search_4(int a[], int n, int key) { int m, l = 0, r = n - 1;//闭区间[0, n - 1] while (l < r) { m = l + ((r + 1 - l) >> 1);//向上取整 cout << ",m " << m; if (a[m] < key) l = m; else r = m - 1; } if (a[l] < key) return l; return -1;}// 同上int binary_search_4_4(int a[], int n, int key) { int res = (int)(lower_bound(a, a + n, key) - a); return (res == 0) ? -1 : res;}int main() { int arr[4] = { 3, 4, 5, 6}; int result1 = binary_search_1(arr, 4, 5); cout << " result1:" << result1 << endl; int result2 = binary_search_2(arr, 4, 5); cout << " result2:" << result2 << endl; int result3 = binary_search_3(arr, 4, 5); cout << " result3:" << result3 << endl; int result4 = binary_search_4(arr, 4, 5); cout << " result4:" << result4 << endl; return 0;}
下面是Python实现
import math# 第一个小于k的数def smallk(seq, k): lo, hi = 0, len(seq) while lo < hi: mid = math.ceil((lo + hi) / 2) # 注意需要向上取整,而//是向下取整 if seq[mid] >= k: hi = mid - 1 else: lo = mid return lo# 相同元素中第一个数def firstequalk(seq, k): lo, hi = 0, len(seq) while lo < hi: mid = (lo + hi) // 2 if seq[mid] < k: lo = mid + 1 else: hi = mid return lo# 第一个大于k的数def biggerk(seq, k): lo, hi = 0, len(seq) while lo < hi: mid = (lo + hi) // 2 if seq[mid] <= k: lo = mid + 1 else: hi = mid return lo# 相同元素中最后一个数def lastequalk(seq, k): lo, hi = 0, len(seq) while lo < hi: mid = (lo + hi) // 2 if seq[mid] > k: hi = mid - 1 else: lo = mid return lo