二分查找算法

我们在写程序的时候,经常会遇到这样一类问题:在一个数组中查找一个数是不是存在。比如在下图的数组中,查找8是不是存在:
在这里插入图片描述
如果不要求效率,我们最一般的查找方法就是顺序查找,依次查看a[0], a[1], …, a[n-1],检查是不是等于8。这样对于长度为n的数组,平均查找长度是n/2。

如果数组是有序的,比如是递增的,就像上图[1, 2, 3, 4, 5, 7, 8, 10, 11, 13]一样的话。我们就有效率更高的查找算法,叫做二分查找。例如还是在上面数组中查找8:
在这里插入图片描述
二分查找会这么进行:我们面对的数组是a[0]-a[9],第一步:我们直接找位于中间的数a[4],发现a[4]=5。我们要找的数是8,比5大,所以我们知道如果8在数组里,那它肯定在a[5]~a[9]这几个数的范围里。

第二步:我们找a[5]-a[9]这个范围里位于中间的数a[7],发现a[7]=10,比我们要找的8大。所以我们知道如果8在数组里,那它肯定在a[5]~a[6]这几个数的范围里。

第三步:我们找到a[5]-a[6]这个范围里位于中间的数a[5],发现a[5]=7,比我们要找的8小。所以我们知道如果8在数组里,那它肯定是a[6]这个数。

第四步也是最后一步。我们查看a[6]的值,发现a[6]=8,于是我们找到了8的位置。假如我们这时发现a[6]不是8,则说明8没有在这个数组里。

二分查找又叫“折半查找”。因为我们每进行一步,也就是查看一个元素的数值,都会使得后面需要检查的范围缩小一半。

有人可能会问,为什么不把a[0]~a[n-1]都扔到哈希表里,这样查找一个元素在不在数组里不是O(1)就能知道吗?

实际上二分查找适用于的情况中,我们往往并不真的知道a[0]-a[n-1]的具体值是多少。我们可以用一个函数去计算a[i]的值,比如a[i]=f(i)。但是计算f(i)的时间复杂度特别高,所以我们没办法把a[0]~a[n-1]的值都算出来。

但是我们知道a[0]~a[n-1]有一个很重要的性质,就是这个数列是单调增的。所以我们可以用二分查找的算法去查找X,在查找的过程中我们只需要计算最多O(logN)+1个a[i]的值,就能找到X的位置。

比如看我们一开始的例子。在查找8的过程中,实际上我们只用到了a[4], a[5], a[6], a[7]的值,其他的值例如a[1], a[9]等,我们根本没看。换句话说不用计算它们的值。

而如果我们想用哈希表,就不得不把a[0]~a[n-1]的值都先计算出来。计算n个元素的值,比计算log(n)个元素的值,复杂度高多了。

二分查找的代码是这个样子:

#include<iostream>
using namespace std;
int n,x,a[100000];
int binary_search(int a[],int n,int x){
    int l = 0;
	int r = n - 1;
	int ans = -1;
	while(l<=r){
		int mid = l + (r - l) / 2;
		if(a[mid] == x){
			ans = mid;
			break;
		}
		if(a[mid] < x)
		    l = mid + 1;
		else
		    r = mid - 1;  
	} 
	return ans;
}
int main()
{
	cin >> n >> x;
	for(int i = 0;i < n; i++)
	    cin >> a[i];
	int ans = binary_search(a, n, x);
	cout << ans << endl;
	return 0; 
}

主要看一下binary_search这个函数。它有三个参数,第一个参数是一个数组a,第二个参数是数组的长度n,第三个参数是待查找的数x。它的返回值是x在数组a中的下标;如果x不在a数组中,返回-1。

注意的一点是,我求mid用的是mid=l+(r-l)/2,而不是mid=(l+r)/2,这是因为:我们举个例子,比如n=2000000000。因为int最大是2147483647,大于n,所以我们可能在二分的代码中放心使用int。但是大家注意,在二分的过程中,可能l的值是1999999998, r的值是1999999999;注意l和r都没有超过int的范围,但是在计算m时,l+r就超过int范围了,导致m计算错误。整个算法挂掉。改成int mid = l + (r – l) / 2; 这样只要n没有超过int范围,那么中间的计算就不会有溢出危险。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值