您当前的位置:首页 > 电脑百科 > 程序开发 > 算法

算法浅谈——人人皆知却很多人写不对的二分法

时间:2019-12-27 10:16:35  来源:  作者:

 

1

 

二分法可以说是鼎鼎大名,哪怕是没有学过编程的同学,也许说不上来二分法这个名字,但是对于其中的精髓应该都是有所了解的。不了解的同学也没关系,我一句话就能交代清楚:我们每次将一个集合一分为二,每次舍弃其中一半。

 

早在两千多年前,庄子就搞清楚了二分法的精髓,他说:一尺之棰,日取其半,万世不竭。从这个角度来说,二分法可能是这个世界上最古老的算法之一了。


二分法不仅古老,而且在计算机系统当中非常常见,许多系统当中都用到了二分法的思想。除此之外,在面试的时候,二分法的算法题也是常客。因为二分法本身不复杂,几乎人人都会,但是对二分法的使用能力却各有不同。出二分法的题,可以真实考察面试者的算法能力和编程功底。

 

不说比较困难的算法题想不出思路,就说最简单没有任何难度的纯二分,在面试的时候,出错的写出bug的也大有人在。

 

很多人会觉得奇怪,二分法这么简单的算法,真的有人写不出来吗?

 

还真的有,原因也很简单,恰恰就是二分法太简单了。

无论是在算法导论还是在一些其他的算法教材当中,关于二分法的描述都不多,详细的会有一些图例展示一下二分法的思想,简单的就用几句话描述一下原理,最后再展示一下代码,就完事了。读者在学的时候也是一样,看了一眼原理,哦,非常简单,再看一眼代码,只有三四行,差不多一眼就能记住,那就丢在一边吧。到了真正上手的时候,问题一下就暴露了出来。

算法浅谈——人人皆知却很多人写不对的二分法

 


二分法最常见的问题有两个,一个是二分的区间边界不清楚,另一个是二分查找的结果不明确。我想,这两个问题是前几次实现二分法的时候,一定会遇到的。遗憾的是,目前的教材当中对于这两个问题介绍都不多,都只有代码,留给读者自行揣摩。

 

2

 

我们先说第一个问题——边界

 

早在小学我们就学过,用l表示区间左边界,r表示区间右边界,mid=(l + r) / 2表示二分的中间点。这个在数学里非常明确,但在编程的时候,有一个隐藏的问题被忽略了。究竟这个区间是闭区间呢,还是开区间呢,还是半开半闭区间或者是半闭半开区间?如果这个问题不想清楚,想要一次性写出没有bug的代码,老实说很不容易。

首先,二分终止的条件究竟怎么写,是while (l < r) 还是 while (l <= r) 还是别的?还有,在搜索的时候,我们究竟要不要将a[mid] == v的情况单独判断?我们是判断a[mid] < v还是a[mid] <= v?假设我们选择用a[mid] <= v,得到的结果为true。我们知道答案应该在区间的右半边,我们需要舍弃左边的区间。应该对l赋值,但是我们是赋值成l = m呢还是l=m + 1呢?又是为什么呢?

你看,如果l和r表示的区间不考虑清楚,我们在实际写代码的时候就会遇到这样棘手的问题。坑爹的是,当我们为这些边界头疼的时候,我们并不能意识到这是因为我们没有搞清楚表示区间的方法导致的。往往会觉得是自己不够熟悉。

 

显然,要解决这个问题需要确定l和r表示的区间种类。那么到底应该选择什么区间呢?是左闭右开,还是全闭,还是左开右闭呢?

 

答案有点出人意料,都行

 

理论上来说,不论选什么样的区间,只要代码得当,都是可以的,可以说是完全看个人喜好。不过我个人推荐左闭右开,原因很简单,这个和编程当中的数组定义的情况一致。我们都知道,在代码的世界里,数组是从0开始的,一个长度为10的数组,最后一个元素的下标是9。如果使用左闭右开区间,我们将l=0,r=数组长度,就完成了初始化,如果用闭区间,r=长度-1,不免显得有些多余。

 

假设我们确定了使用左闭右开区间,我们再来看前面说的两个问题。

区间确定了,终止条件也就明确了,左闭右开区间[l, r)不为空的话,r 至少大于等于l + 1。我们要在区间长度大于1的时候执行二分,所以二分的循环条件应该是while (l + 1 < r)。

 

3

 

那么while里的判断条件呢?

我们列举一下,a[mid] 和v的大小关系无非只有三种。

第一种a[mid] = v,很简单,mid就是我们要查找的结果,直接返回。

 

第二种a[mid] < v,说明我们应该取右边的区间,由于l的位置可以取到,而mid已经不是答案了,所以l = mid + 1。

 

第三种a[mid] > v,应该取左边的区间,mid不是答案,但是由于r指向的位置本身就不在候选区间里,所以r = mid,而不是mid-1,因为mid-1可能是答案,而r处的位置是取不到的。

到这里,似乎一切完美,我们可以很顺利地写出代码了。但是还没有结束,依然还有一个小问题。

 

算法浅谈——人人皆知却很多人写不对的二分法

 

 

前文说了,a[mid]和v的关系有三种,当a[mid] = v的时候,我们就找到了答案。从这个角度来看,我们二分的时候,通过l和r缩小区间的范围,通过mid来寻找答案。但是既然我们已经折半区间的大小了,那么当区间长度为1的时候,剩下的就是答案,我们为什么还需要通过mid去查找答案呢?如果我们就想通过区间本身来查找答案,那么应该怎么办呢?

 

也不难,我们需要把a[mid]小于和等于v的两种情况合并,由于a[mid]可能等于v,所以我们不能跳过mid这个位置,l = mid + 1 应该写成l = mid,于是整个代码也就出来了:

defbinary_search(a,v):l,r=0,len(a)whilel+1<r:m=(l+r)/2ifa[m]<=v:l=melse:r=m//通过a[l]==v判断v不存在与a数组当中的情况returnl

 

4

 

可能会有同学好奇,如果我不使用左闭右开,而使用闭区间呢,代码又该怎么写?

其实只要把区间想清楚了,写出来也不难。

defbinary_search(a,v):l,r=0,len(a)-1whilel<=r:m=(l+r)/2ifa[m]==v:returnmifa[m]<v:l=m+1else:r=m-1//表示不存return-1

不过还有一个小问题,为什么闭区间形式的二分法的判断推荐是while (l <= r)呢?换成while (l < r)行不行?这个问题就留给大家思考。

 

二分法虽然简单,但这些细节都理解清楚也并不容易,在算法领域当中,如果细节没有理解到位,阴沟里翻船是非常平常的事情。希望今天的文章能对大家有所帮助。



Tags:二分法   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
import matplotlib.pyplot as plta=3b=-2x=(a+b)/2.0 # 中点def f(x): # 定义方程式y=x-2**0.5return(y)u=max([a,b])l=min([a,b])i=0z=[]est=[]# 循环体while abs(f(x...【详细内容】
2021-09-03  Tags: 二分法  点击:(478)  评论:(0)  加入收藏
1 二分法可以说是鼎鼎大名,哪怕是没有学过编程的同学,也许说不上来二分法这个名字,但是对于其中的精髓应该都是有所了解的。不了解的同学也没关系,我一句话就能交代清楚:我们每...【详细内容】
2019-12-27  Tags: 二分法  点击:(57)  评论:(0)  加入收藏
▌简易百科推荐
前言Kafka 中有很多延时操作,比如对于耗时的网络请求(比如 Produce 是等待 ISR 副本复制成功)会被封装成 DelayOperation 进行延迟处理操作,防止阻塞 Kafka请求处理线程。Kafka...【详细内容】
2021-12-27  Java技术那些事    Tags:时间轮   点击:(1)  评论:(0)  加入收藏
博雯 发自 凹非寺量子位 报道 | 公众号 QbitAI在炼丹过程中,为了减少训练所需资源,MLer有时会将大型复杂的大模型“蒸馏”为较小的模型,同时还要保证与压缩前相当的结果。这就...【详细内容】
2021-12-24  量子位    Tags:蒸馏法   点击:(11)  评论:(0)  加入收藏
分稀疏重建和稠密重建两类:稀疏重建:使用RGB相机SLAMOrb-slam,Orb-slam2,orb-slam3:工程地址在: http://webdiis.unizar.es/~raulmur/orbslam/ DSO(Direct Sparse Odometry)因为...【详细内容】
2021-12-23  老师明明可以靠颜值    Tags:算法   点击:(7)  评论:(0)  加入收藏
1. 基本概念希尔排序又叫递减增量排序算法,它是在直接插入排序算法的基础上进行改进而来的,综合来说它的效率肯定是要高于直接插入排序算法的;希尔排序是一种不稳定的排序算法...【详细内容】
2021-12-22  青石野草    Tags:希尔排序   点击:(6)  评论:(0)  加入收藏
ROP是一种技巧,我们对execve函数进行拼凑来进行system /bin/sh。栈迁移的特征是溢出0x10个字符,在本次getshell中,还碰到了如何利用printf函数来进行canary的泄露。ROP+栈迁移...【详细内容】
2021-12-15  星云博创    Tags:栈迁移   点击:(22)  评论:(0)  加入收藏
一、什么是冒泡排序1.1、文字描述冒泡排序是一种简单的排序算法。它重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地...【详细内容】
2021-12-15    晓掌柜丶韶华  Tags:排序算法   点击:(16)  评论:(0)  加入收藏
在了解golang的map之前,我们需要了解哈希这个概念。哈希表,又称散列表(Hash table),是根据键(key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算出一个键值的函数,将...【详细内容】
2021-12-07  一棵梧桐木    Tags:哈希表   点击:(14)  评论:(0)  加入收藏
前面文章在谈论分布式唯一ID生成的时候,有提到雪花算法,这一次,我们详细点讲解,只讲它。SnowFlake算法据国家大气研究中心的查尔斯&middot;奈特称,一般的雪花大约由10^19个水分子...【详细内容】
2021-11-17  小心程序猿QAQ    Tags:雪花算法   点击:(24)  评论:(0)  加入收藏
导读:在大数据时代,对复杂数据结构中的各数据项进行有效的排序和查找的能力非常重要,因为很多现代算法都需要用到它。在为数据恰当选择排序和查找策略时,需要根据数据的规模和类型进行判断。尽管不同策略最终得到的结果完...【详细内容】
2021-11-04  华章科技    Tags:排序算法   点击:(40)  评论:(0)  加入收藏
这是我在网上找的资源的一个总结,会先给出一个我看了觉得还行的关于算法的讲解,再配上实现的代码: Original author: Bill_Hoo Original Address: http://blog.sina.com.cn/s/bl...【详细内容】
2021-11-04  有AI野心的电工和码农    Tags: KMP算法   点击:(36)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条