vlambda博客
学习文章列表

二分查找,教你如何提高搜索效率

目录

二分查找,是程序员入门必会的一个高效搜索算法。本文将对二分查找,以及时间复杂度等概念进行讲解。





1

什么是算法和时间复杂度


在计算机科学中,算法是指在有限时间内解决一个问题的一系列确定的指令。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。

一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)



2

 二分查找


我们通过一道题来理解时间复杂度。有个整数x在1-100的范围内,每当你给出一个数y时,能够得到y比x大还是y比x小的信息。你的任务是用尽可能少的次数猜到x的准确值。

最简单粗暴的做法:从1开始尝试,试到100。这种做法,最终一定能得到结果,但是,最坏的情况下,假如这个数刚好是100,需要试100次。这种方法也称为顺序查找。

聪明的做法:每次猜最中间的数,得到这个数比目标大还是小的结果后,在目标所在的区间继续猜中间的数。这样每次至少能排除一半的数。假如目标数是100,1到100存储在数组a[0]到a[99]中。

第一次从50开始猜,会告诉你小了,因此可以排除1-50之间的数,一下子排除了一半。

第二次猜75,会告诉你小了,排除51-75

第三次猜88,会告诉你小了,排除76-88

第四次猜94,会告诉你小了,排除89-94

第五次猜97,会告诉你小了,排除95-97

第六次猜99,命中。

我们猜的范围每次都能够减少一半,因此这个查找方式也被叫做二分查找,又称折半查找。它适用于有序数组中的查找。

二分查找的代码如下:

二分查找,教你如何提高搜索效率



3

 效率分析


由于二分查找每次都能够削减一半的范围,我们搜索的范围为100,小于2^7=128,因此最多只需要搜索7次就可以找到。

假设搜索的范围为1到n,那么,方案1的顺序查找最坏需要遍历n次,而方案二的二分查找法最坏需要遍历logn次。这个效率明显不是一个数量级的。例如n=100W时,穷举法需要100W次搜索,而二分查找只需要20次。

借助这个例子,我们可以引出时间复杂度的概念。一般来说,我们只考虑一个算法在最坏情况下的时间复杂度,因为他可以帮助我们粗略估计算法最长需要运行的时间。用大O来表示这个时间复杂度。

比如,方案1中的穷举法,对应的时间复杂度为O(n)。方案2的二分查找,对应的时间复杂度为O(logn)。除了这两种时间复杂度之外,还有O(1),O(n^2),O(nlogn)等时间复杂度,O(1)表示最坏情况在常数时间内。例如,一次简单的加法,简单的除法,数组中通过下标访问。

如果我们的程序中,有很多部分,其中有一些是O(1)的,有些是O(n)的,有些是O(logn)的。这种情况下,怎么估计程序的运行时间呢?最直接的,把他们加起来,T(n)=O(1)+O(n)+O(logn)。刚刚我们也提到了,方案1的穷举法比方案2的二分搜索要差很多,由于我们是要看最坏情况下程序的情况,所以,我们看耗时最久的,也就是T(n)=O(n)。这能够说明,O(n)这部分的算法占整个程序运行时间的大头。如果我们需要优化我们的代码,就要从这部分入手。当然,O(n)的复杂度其实已经算好的了,后面还有更复杂的,比如O(n^2),O(n^3)之类的复杂度。

以这段代码为例子,

39-41行的循环中,循环n次,执行循环体需要O(1)的复杂度,总体是T(n)=O(n)*O(1)=O(n)

42行,调用了二分搜索,复杂度为O(logn)

43-45行的循环中,循环n次,执行循环体需要O(logn)的复杂度,整体时间复杂度为T(n)=O(n)*O(logn)=O(nlogn)

最终,整个程序总的时间复杂度为T(n)=O(n)+O(logn)+O(nlogn)=O(nlogn),可以看出这段程序最耗时的部分是43-45行。