一、概述
二分查找(Binary Search,也称折半查找)——针对有序数据集合的查找算法
1、基本思想
类似分治思想,每次都通过跟区间的中间元素进行对比,将代查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0(不存在该元素)。
2、时间复杂度分析——O(logn)
不妨假设数据量为n,每次查找后数据量均为原来的1/2,最坏的情况下,直到查找区间被缩小为空,才停止。设经过k次区间缩小操作区间缩小到1,可得 k=log2n 时间复杂度为O(k),也就是O(logn)。
O(1) 常量级时间复杂度的算法可能还没有O(logn)的算法执行效率高。因为:大O标记法表示时间复杂度时,会省略掉常数、系数和低阶,O(1)可能表示一个非常大的常量值,eg: O(10000)。
二、简单实现
二分查找最简单的情况:有序数组中不存在重复元素
1、递归方法实现
#include<iostream>
using namespace std;
int BinarySearchRecursive(int *array, int low, int high, int key)
{
if(low > high)
return -1;
// int mid = (low + high) / 2; 若low和high比较大,两者之和就可能溢出
int mid = low + (high - low) / 2;
// 若性能有要求,可将除以2操作转化为位运算,以提升效率
// int mid = low + ((high - low)>>1)
if(array[mid] == key)
return mid;
else if(array[mid] < key)
return BinarySearchRecursive(array, mid+1, high, key);
else
return BinarySearchRecursive(array, low, mid-1, key);
}
int main()
{
int array[10];
for(int i = 0; i < 10; i++)
array[i] = i;
cout<< "Recursive:"<<endl;
cout<<"postion:"<<BinarySearchRecursive(array,0,9,4)<<endl;
return 0;
}
2、非递归方法
int BinarySearch(int *array, int aSzie, int key)
{
if(array == NULL || aSize == 0)
return -1;
int low = 0;
int high = aSize - 1;
int mid = 0;
while(low <= high)
{
mid = low + (high - low) / 2;
if(array[mid] == key)
return mid;
else if(array[mid] < key)
low = mid + 1;
else
high = mid - 1;
}
return -1;
}
3、应用场景的局限性
适用于:插入、删除操作都不频繁,一次排序多次查找且数据量较大的场景。
-
二分查找依赖的是顺序表数据结构,也就是数组(可以按照下标随机访问元素)。
-
二分查找的数据是有序的:排序算法时间复杂度最低为O(nlogn),若动态变化的数据集合,不再适用。
-
适合数据量较大的场景,而非特别大。因为数组为了支持随机访问的特性,要求内存空间连续。
特例:若数据之间的比较操作特别耗时,不管数据量大小,都推荐使用二分查找。同on个过尽可能较少比较次数来提升性能。
三、应用实例
1、实例
假设我们有 1000 万个整数数据,每个数据占 8 个字节,如何设计数据结构和算法,快速判断某个整数是否出现在这 1000 万数据中? 我们希望这个功能不要占用太多的内存空间,最多不要超过 100MB,你会怎么做呢?
2、分析
由于每个数据占 8 个字节,内存占用差不多为80M < 100M,符合内存限制。
==》先从小到大排序,然后利用二分查找算法找数据。
注意:散列表、二叉树这些支持快速查找的动态数据结构。但是,在该情况却不行的。虽然在大部分情况下,用二分查找可以解决的问题,用散列表、二叉树都可以解决。但是都会需要比较多的额外的内存空间。所以,如果用散列表或者二叉树来存储这
1000 万的数据,用 100MB 的内存肯定是存不下的。而二分查找底层依赖的是数组,除了数据本身之外,不需要额外存储其他信息,是最省内存空间的存储方式,所以刚好能在限定的内存大小下解决这个问题。
四、二分查找的进级(变形问题)
前提:数据都是从小到大排列的,且数据中存在重复的数据。
1、常见的变形问题
- 查找第一个值等于给定值的元素
- 查找最后一个值等于给定值的元素
- 查找第一个大于等于给定值的元素
- 查找最后一个小于等于给定值的元素
2、查找第一个值等于给定值的元素
(1)方法一
int bsearch1(int *array, int aSize, int key)
{
int low = 0;
int high = aSize - 1;
while (low <= high) {
int mid = low + ((high - low)/2);
if(array[mid] > key)
high = mid - 1;
else if(array[mid] < key)
low = mid + 1;
else
// 当array[mid]=key时,
// 需要确认一下这个 array[mid] 是不是第一个值等于给定值的元素
{
// 若mid等于0,说明该元素为第一个元素;
// array[mid]的前一个元素array[mid-1]不等于key,说明该元素为第一个元素;
if((mid == 0)||(array[mid-1]!=key))
return mid;
// 否则更新区间
else
high = mid - 1;
}
}
return -1;
}
(2)方法二
int bsearch2(int *array, int aSize, int key)
{
int low = 0;
int high = aSize - 1;
while (low <= high)
{
int mid = low + ((high - low) / 2);
if(array[mid] >= key)
{
high = mid - 1;
}
else
{
low = mid + 1;
}
}
if(array[low] == key)
return low;
else
return -1;
}
3、查找最后一个值等于给定值的元素
int bSearch(int *array,int aSize,int key)
{
int low = 0;
int high = aSize - 1;
while(low <= high)
{
int mid = low + ((high - low) / 2);
if (array[mid] >= key)
high = mid - 1;
else if (array[mid < key]) {
low = mid + 1;
}
else
{
if((mid == aSize - 1)||(array[mid+1] != key))
return mid;
else
low = mid + 1;
}
}
return -1;
}
4、查找第一个大于等于给定值的元素
int bSearch(int *array,int aSize,int key)
{
int low = 0;
int high = aSize - 1;
while(low <= high)
{
int mid = low + ((high - low) / 2);
if (array[mid] >= key)
{
if((mid == 0)||(array[mid-1] < key))
return mid;
else
high = mid - 1;
}
else
{
low = mid + 1;
}
}
return -1;
}
5、查找最后一个小于等于给定值的元素
int bSearch(int *array,int aSize,int key)
{
int low = 0;
int high = aSize - 1;
while(low <= high)
{
int mid = low + ((high - low) / 2);
if (array[mid] > key)
{
high = mid - 1;
}
else
{
if((mid == aSize - 1)||(array[mid+1] > key))
return mid;
else
low = mid + 1;
}
}
return -1;
}