![66e2fad34e0ba4c0d36d53608c3cbd9e.png](https://img-blog.csdnimg.cn/img_convert/66e2fad34e0ba4c0d36d53608c3cbd9e.png)
当您要检查某个元素是否在列表中时,有很多方法可以解决相同的问题。可以通过线性查找和二分查找来完成,但是要猜测哪个更快。
为什么?
如果你最近参加过面试,你就会知道二分查找是面试官的最爱。
您为什么要花时间学习二分查找? C ++编程朋友可能已经告诉过您。 Python很慢。 您想确保自己的程序不会比所需的速度慢。
学习Python时,您将学习进行线性查找以检查元素是否在列表中。 当您学习编码时很好,但是如果列表中有60.000.000个元素会发生什么呢?
如果在包含11个元素的列表中进行线性查找,则必须遍历所有11个元素。 如果您使用二分查找,最终可能要进行2次迭代,具体取决于您要查找的内容。 请参见下面的图形。
显而易见,哪种方法更快。
开始学习Python时,您很可能已经使用了一百次列表。 检查列表中是否有一个值是一项正常的任务,您之前已经看到过:
my_list = [1,2,3,3,5,11,12]if 11 in my_list: return Truereturn False
或者
my_list = [1,2,3,3,5,11,12]for each in list: if each==11: return Truereturn False
让我们开始看看如何实现二分查找。
怎么做?
让我们看看二分查找是如何工作的。
首先,我们需要确保列表是有序的。您可以使用.sort()或sorts()对列表进行排序,我使用.sort()在适当的地方修改列表。如果出于某种原因需要一个新列表,或者不想篡改原始列表,可以使用sorted()
这是我们的测试内容:
bin_list = [1,2,3,5,6,9,11,12,15,20,22]search_value_a = 15
我们要寻找15的值。
我们的起点。具有最小值和最大值的列表:
![6e05d64795128ebc781b4ec8e6aa7362.png](https://img-blog.csdnimg.cn/img_convert/6e05d64795128ebc781b4ec8e6aa7362.png)
当我们做二分查找时,我们从寻找列表中的中间元素开始:
![5114c0dafdc461ca6b9c4df9a64a3c22.png](https://img-blog.csdnimg.cn/img_convert/5114c0dafdc461ca6b9c4df9a64a3c22.png)
中间索引为5,值为9。首先我们要知道9是不是我们要找的数字。记住,我们要找的是15。如果不是,我们检查它是更高还是更低。在这个例子中,9比15小,所以我们需要设置一个新的最小值点。我们知道我们不再需要担心列表的下半部分。新的最小点将被设置为列表上部的第一个可能的项。
![b55fed0935b9c054a9ce213c934abfaf.png](https://img-blog.csdnimg.cn/img_convert/b55fed0935b9c054a9ce213c934abfaf.png)
使用新的中点,我们检查这是否是我们要寻找的数字。在这种情况下,正好是15,这样这次查找就完成了。
如果我们要找的是2,而第一个中间值是9,你觉得这个算法会怎么做?你是对的。取而代之的是max指数。
![778cf0ba182ad652fbbb2f00e2e235b4.png](https://img-blog.csdnimg.cn/img_convert/778cf0ba182ad652fbbb2f00e2e235b4.png)
![61c22059988da4d2e18682f4a779693b.png](https://img-blog.csdnimg.cn/img_convert/61c22059988da4d2e18682f4a779693b.png)
![9eff8a6f978b40afe3c2812cd3709122.png](https://img-blog.csdnimg.cn/img_convert/9eff8a6f978b40afe3c2812cd3709122.png)
![17eb23520be01b4e51e5eefe9fefc7d7.png](https://img-blog.csdnimg.cn/img_convert/17eb23520be01b4e51e5eefe9fefc7d7.png)
代码
通俗的流程解释如下:
用列表和目标作为参数创建函数。确保列表是有序的。
获取列表长度- 1为最大,0为开始。循环将:
1. 获得新的中间值
1. 检查中间值是否高于或低于目标值。
1. 检查结束后,将最小值或最大值移到中间。
1. 如果middle == target,返回True
1. 如果我们到达列表的末尾,则目标不在列表中
下面看看代码实现:
import randomdef binary_search(input_list , target_value): ''' Function executing binary search to find if element is in a list. parameters: - list - target return value: True/False (bool) ''' input_list.sort() min_index = 0 max_index = len(input_list) -1 while max_index >= min_index: mid_index =(max_index+min_index)//2 if input_list[mid_index] == target_value: return True elif input_list[mid_index] < target_value: min_index = mid_index+1 else: max_index = mid_index-1 return Falsedef main(): #bin_list = list(range(6,501)) bin_list = [1,2,3,5,6,9,11,12,15,20,22] search_value_a = 15 search_value_b = 7 assert binary_search(bin_list,search_value_a) == True assert binary_search(bin_list,search_value_b) == Falseif __name__ == '__main__': main()
我们已经创建了一个具有两个参数的函数。列表和目标值。目标值就是我们要找的数字。这个列表就是我们要遍历的,用来寻找数字的列表。
def binary_search(input_list , target_value):
如果我们找到了目标值,我们将返回True。如果不是,我们将返回False。
我们要做的第一件事是对列表进行排序,并定义列表的最小索引和最大索引。
input_list.sort()min_index = 0max_index = len(input_list) -1
我们使用len(list)-1的原因是Python从0开始索引。测试列表的长度是11,但是最后一个索引是[10]。
现在,让我们进入主要的功能,循环:
while max_index >= min_index: mid_index =(max_index+min_index)//2 if input_list[mid_index] == target_value: return True elif input_list[mid_index] < target_value: min_index = mid_index+1 else: max_index = mid_index-1
只要最大值不大于最小值,我们就继续。如果循环停止了,那就意味着我们已经折叠了列表,使得最大值小于最小值。此时,没有必要查找这个值,因为没有更多的列表了。
mid被设置为最大值和最小值的平均值。请注意我们是如何使用整数除法的,例如7//2将是3而不是3.5。这样,我们总是为索引得到一个干净的整数。
如果带有中间索引的列表项的值等于我们的目标值,我们就成功了!返回True,然后退出。
如果这个值小于目标值,我们知道我们必须把最小索引推到那个点。因此新的最小值是中间+1
如果该值不等于或小于目标值,则会较大。这意味着我们可以删除列表的顶部并将最大索引下压。max被设置为中间-1
如果您觉得难以理解,可以在代码中添加print(),以获得索引跳跃的可视化表示。
在while循环的midindex =(maxindex+min_index)//2之后添加这个:
print (f'min: {min_index} , mid: {mid_index} , max: {max_index}')
但是它更快吗?
该函数的时间复杂度为O(n),其中n为链表的长度。为了检验哪种查找更快,我们可以计算二分查找相对于线性查找的时间。
![f7a8d6bfb86dda55b2a47a72c2c92024.png](https://img-blog.csdnimg.cn/img_convert/f7a8d6bfb86dda55b2a47a72c2c92024.png)
首先,我们需要写出一个线性查找函数:
def linear_search(input_list, target_value): for each in input_list: if each==target_value: return True return False
下面我们可以进行对比了:
def binary_performance(): run_setup = '''from __main__ import binary_search''' run_code = '''bin_list = list(range(6,501))binary_search(bin_list,15)''' performance = timeit.repeat(setup = run_setup, stmt = run_code, repeat = 3, number = 10_000) print(f'binary search performance = {round(min(performance),2)}')def linear_performance(): run_setup = '''from __main__ import linear_search''' run_code = '''lin_list = list(range(6,501))linear_search(lin_list,15)''' performance = timeit.repeat(setup = run_setup, stmt = run_code, repeat = 3, number = 10_000) print(f'linear search performance = {round(min(performance),2)}')binary_performance()linear_performance()
![b0159e80af899b99e438867c7217a58c.png](https://img-blog.csdnimg.cn/img_convert/b0159e80af899b99e438867c7217a58c.png)
陷阱
如果您运行上面的代码(与原始代码合并),您将看到线性查找更快了。这是什么魔法?
有几个问题给二分查找带来了困难。
排序
列表的长度
低于目标的值
以上所有因素,让线性领先。现在让我们继续排序,先改变列表长度:
bin_list = list(range(1,10000))lin_list = list(range(1,10000))
![988e3f337618622d7b1b8c5b01a97109.png](https://img-blog.csdnimg.cn/img_convert/988e3f337618622d7b1b8c5b01a97109.png)
线性查找仍然占上风。让我们对函数进行排序,并在将列表传递给函数之前对其进行排序。(这对线性查找是不公平的,因为线性并不依赖于排序列表)。我们所要做的就是在列表排序时注释掉它。
![21dfb799429bf90388f9d722960aff2c.png](https://img-blog.csdnimg.cn/img_convert/21dfb799429bf90388f9d722960aff2c.png)
二者速度比较接近了。
如果我们将目标值移动到7,500呢?现在我们的偏差很明显,因为我们真的想让二分更快。
![6fb373f450934ad2336079c05c057182.png](https://img-blog.csdnimg.cn/img_convert/6fb373f450934ad2336079c05c057182.png)
这次的差异是极端的。下面的最后一个例子将使情况更加公平。
让我们以随机长度和随机目标创建一个随机列表。然后我们将为这两个函数运行100,000次。
test_list = list(range(1,random.randint(2,50000)))test_number = random.randint(2,50000)binary_search(test_list,test_number)
![89e134e4141f1c1fb6da228044a5b49e.png](https://img-blog.csdnimg.cn/img_convert/89e134e4141f1c1fb6da228044a5b49e.png)
![d9e64d8e2321af83ed9dc131411ac0bb.png](https://img-blog.csdnimg.cn/img_convert/d9e64d8e2321af83ed9dc131411ac0bb.png)
现在让我们运行10万次!,我相信这些结果。上图是排序后结果,下图需要进行排序
总结
二分比线性快吗?是的,但要看情况而定。
如果有人告诉你二分查找更快,那是因为它通常是更快的。
和往常一样,你必须看一看设置,不要每次都去找一个单一的解决方案,因为它"是最好的"。
我们从实验中看到,这取决于你在做什么。如果您有一个简短的列表,或者如果您在列表的下半部分寻找元素,那么执行线性查找可能会更好。
这也是编程之美。你不应该在不知道为什么的情况下使用一种方法来做某事。如果你还不知道二分查找,现在你有了另一个工具来做查找。只要你觉得它有用,就使用它。
我希望我们能在一件事上达成一致。二分查找是相当酷的!
作者:Martin Andersson Aaberge
deephub翻译组