关于二分查找

二分查找实现起来确实非常容易出错,下面贴几个我实现过的版本。

实现思想:
二分查找是分治思想应用的一个典型例子,对于递归实现,可以画一个二分查找树来辅助理解,这棵动态生成树并不像归并排序那样需要全部遍历(前序),而是根据条件判定生成结点且仅遍历其中一个结点或者中止遍历。


【版本一】
直观递归(成功则返回查找元素key的下标,不成功则返回-1或者返回小于key的最大元素下标)
递归出口有两个,一个是查找失败则一直回溯,返回的-1是栈顶函数的-1; 一个是查找成功也是一直回溯,返回的mid也是栈顶函数的mid
return -1;前面的else可以略去,比如几次查找后成功从第一个if里面的某条分支中返回一个确定值,分析栈底函数,直接返回这个值后会结束函数而不会继续执行return -1,当然加上else更加清晰一些

这个版本递归思想很直白,我比较喜欢

int
bSearch(int a[], int l, int r, int key)
{
	int mid;
	
	if (l <= r) {
		mid = l + (r-l)/2; //use "mid=(l+r)/2;" may overflow
		if (key == a[mid]) 
			return mid;
		else if (key < a[mid])
			return bSearch(l, mid, key);
		else
			return bSearch(mid+1, r, key);		
	}
	else	
		return -1;
}



【版本二】
也是递归(成功则返回key下标,不成功返回-1)
原理就是把mid初始为-1,如果查找成功则返回的是第一个if语句里面的mid,即mid被修改为非-1;如果查找失败返回没有被修改过的mid或第一个if里面被修改过但最终修改为-1的mid。
语句return mid;前面不可以有else,假如有else,经过几次查找成功后,抵达栈顶函数时mid可能由第一个if里面第二条分支语句所赋值,如果有else则丢弃了mid值函数直接退出,因为此时else后的语句不会执行。
这个版本比【版本一】难理解
int
bSearch(int a[], int l, int r, int key)
{
	int mid = -1;
	
	if (l <= r) {
		mid = l + (l-r)/2;
		if (key == a[mid]) 
			return mid;
		else if (key < a[mid])
			mid =  bSearch(l, mid, key);
		else
			mid =  bSearch(mid+1, r, key);		
	}
	return mid;
}


【版本三】
迭代,这个最容易理解,功能也与【版本一】一样

int
bSearch(int a[], int l, int r, int key)
{
	int mid;
	
	while (l <= r) {
		mid = l + (r-l)/2;
		if (key == a[mid]) 
			return mid;
		else if (key < a[mid])
			r = mid;
		else
			l = mid+1;
	}
	return -1;
}



几个解释:

1、为什么查找失败时不返回大于key的最小元素的下标呢?l不是刚好指到那里吗?

因为如果key > 有序表的最大元素,这时l只能指向小于key的最大元素,而其他时候l指向大于key的最小元素。所以返回小于key的最大元素下标,则任意情况下失败时的返回值,其意义都是统一的。

【更正】:

当采用[l, mid]和[mid+1, r]划分,且a[l] <= key <= a[r]时,如果查找不成功,那么l等于大于key的最小元素下标,l-1也就等于小于key的最大元素下标。

尤其注意前提条件有两个:一是采用[l, mid]和[mid+1, r]划分;二是满足a[l] <= key <= a[r]。

对于key<a[l]和key>a[r]的情况最好另外处理。


2、为什么当前发生a[mid] != key 时, 不查找[l, mid-1]或[mid+1, r], 而是查找[l, mid]或[mid+1, r]?

对于第一种划分,若仅实现查找成功返回key的下标失败返回-1则是正确的,但是不能返回小于key的最大元素下标,比如查找1 3 5 9 11,查找6失败指向9,查找10失败也指向9,即失败时(r == l)时l指向的元素与key的大小关系不是确定的。因此用后者可以同时实现二分查找失败时返回小于key的最大元素下标或大于key的最小元素下标。注意key的范围。


3、为什么不使用mid = (l+r)/2?

防止(l+r)时溢出,改成l/2+r/2没问题,但不如l+(r-h)/2效率好。当然这里已经假定函数接受的参数是合法的,所有没有作参数检查。但是上述情况是合法的参数调进却可能发生溢出,这就是算法实现有bug了。



补充 :今天做题时才发现自己沙茶了,既然key可能大于a[l],也可能小于a[r]

现在的问题是, 给定整数S、E,满足S < E,如何在有序表中找出下标距离最大的两个元素X,Y,满足S<=X<Y<=E?

可以分5类情况:

设X=a[left],Y=a[right],则有——

若E<a[l] || S>a[r], 则找不到;

若S<=a[l] && a[r]<=E,则left = l; right = r;

若a[l]<=S && E<=a[r],则left = i, 第一个a[i]>=S;  right = i-1, 第一个a[i]>E;

若S<=a[l] && a[l]<E<a[r],则left = l; right = i-1, 第一个a[i]>E;

若E>=a[r] && a[l]<S<a[r],则left = i, 第一个a[i]>S;  right = r;


下面假定只考虑情况3——

1、如果用直接查找,对于左边,则查找到>=S时,存当前下标,对于右边,则查找到>E时,存前一个元素的下标

for (i = 0; a[i] < S, i++) NULL;
left = i;
for (i = 0; a[i] <= E, i++) NULL;
right = i-1;
2、用二分查找呢?

对情况3,有a[l] <= key <= a[r]

int
bSearch(int mark, int a[], int l, int r, int key)
{
	int mid;

	while (l <= r) {
		mid = l+(r-l)/2;
		if (key == a[mid])
			return mid;
		else if (key < a[mid])
			r = mid;
		else
			l = mid+1;
	}
	if (mark == 0)
		return l;//返回大于key的最小元素下标
	else
		return l-1;//返回小于key的最大元素下标
}

查找左边则调用

left = bSearch(0, a, l, r, S);


查找右边则调用

right = bSearch(1, a, l, r, E);




源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码和压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值