14-二分查找的原理和实现

举个例子,A 心里想一个1-1000之间的数,让 B 来猜,B 可以问问题,A 只能回答是或否。怎么猜才能问的问题次数最少?
最笨的方法就是从1开始问,是1吗?是2吗?…是999吗?如果是1,问一次就得到答案了,如果A想的是1000,需要1000次才能得到答案,平均要问500次。
还有一种问法,从中间开始问,一次将范围缩小一半。大于500吗?回答是,大于750吗?回答否,大于625吗? …每次缩小猜测范围到上次的一半,只需要 10次一定可以猜到 A 心里想的数。这种方法就用到了二分的思想。

1、案例一

问题描述:写一个函数 BinarySeach,在包含 size 个元素的、从小到大排序的 int 数组 a 里查找元素 p,如果找到,则返回元素下标,如果找不到,则返回-1。要求复杂度 O(log(n))

显而易见应该用二分查找去做,代码也非常简单,直接上代码

#include<iostream>
using namespace std;

int BinarySeach(int a[], int size,int p)
{
	int left = 0;				//查找区间的左端点
	int right = size - 1;		//查找区间的右端点
	int mid;
	while (left<=right)			//如果查找区间不为空,继续查找
	{
		mid = left + (right - left) / 2;//取查找区间正中元素的下标
		if (p == a[mid])
			return mid;
		if (p > a[mid])
			left = mid + 1;		//设置新的查找区间的左端点
		else
			right = mid - 1;	//设置新的查找区间的右端点
	}
	return -1;					//找不到
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int len = sizeof(a) / sizeof(a[0]);
	cout << BinarySeach(a, len, 8)<<endl;
	return 0;
}

2、问题变化

问题描述:写一个函数 LowerBound,在包含 size 个元素的、从小到大排序的 int 数组 a 里查找比给定整数 p 小的,下标最大的元素。找到则返回其下标,找不到则返回-1

问题分析:看到数组是从小到大排列的,就想到了二分法。每次将数组分一半,如果中间元素大于等于 p,继续在左侧区间寻找,如果中间元素小于p,记录这个位置,在 p 左侧。当区间长度变为0时,该位置就是 p 左侧的第一个元素。

#include<iostream>
using namespace std;

int LowerBound(int a[], int size, int p)
{
	int left = 0;				//查找区间的左端点
	int right = size - 1;		//查找区间的右端点
	int mid,lastpos=-1;			//lastpos为到目前为止找到的最优解
	while (left<=right)			//如果查找区间不为空,继续查找
	{
		mid = left + (right - left) / 2;//取查找区间正中元素的下标
		if (a[mid]>=p)
			right = mid - 1;
		else
		{
			lastpos = mid;
			left = mid + 1;
		}
			
	}
	return lastpos;
}

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9,10,11,12};
	int len = sizeof(a) / sizeof(a[0]);
	cout << LowerBound(a, len, 8) << endl;
	return 0;
}

有一个细节需要注意,在取中间元素的时候,最好使用 int mid = L+(R-L)/2; 如果使用 int mid = (L+R)/2; 在计算 (L+R) 时可能结果过大,超过了 int 的表示范围,溢出了。

3、总结

理解二分法的思想,二分法的变形考法。

猜你喜欢

转载自blog.csdn.net/happyjacob/article/details/87858152
今日推荐