Python实现二分查找与bisect模块详解


Posted in Python onJanuary 13, 2017

前言

其实Python 的列表(list)内部实现是一个数组,也就是一个线性表。在列表中查找元素可以使用 list.index() 方法,其时间复杂度为O(n) 。对于大数据量,则可以用二分查找进行优化。

二分查找要求对象必须有序,其基本原理如下:

      1.从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;

      2.如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。

      3.如果在某一步骤数组为空,则代表找不到。

二分查找也成为折半查找,算法每一次比较都使搜索范围缩小一半, 其时间复杂度为 O(logn)。

我们分别用递归和循环来实现二分查找:

def binary_search_recursion(lst, value, low, high): 
 if high < low: 
 return None
 mid = (low + high) / 2 
 if lst[mid] > value: 
 return binary_search_recursion(lst, value, low, mid-1) 
 elif lst[mid] < value: 
 return binary_search_recursion(lst, value, mid+1, high) 
 else: 
 return mid 

def binary_search_loop(lst,value): 
 low, high = 0, len(lst)-1 
 while low <= high: 
 mid = (low + high) / 2 
 if lst[mid] < value: 
 low = mid + 1 
 elif lst[mid] > value: 
 high = mid - 1
 else:
 return mid 
 return None

接着对这两种实现进行一下性能测试:

if __name__ == "__main__":
 import random
 lst = [random.randint(0, 10000) for _ in xrange(100000)]
 lst.sort()

 def test_recursion():
 binary_search_recursion(lst, 999, 0, len(lst)-1)

 def test_loop():
 binary_search_loop(lst, 999)

 import timeit
 t1 = timeit.Timer("test_recursion()", setup="from __main__ import test_recursion")
 t2 = timeit.Timer("test_loop()", setup="from __main__ import test_loop")

 print "Recursion:", t1.timeit()
 print "Loop:", t2.timeit()

执行结果如下:

Recursion: 3.12596702576
Loop: 2.08254289627

可以看出循环方式比递归效率高。

bisect 模块

Python 有一个 bisect 模块,用于维护有序列表。bisect 模块实现了一个算法用于插入元素到有序列表。在一些情况下,这比反复排序列表或构造一个大的列表再排序的效率更高。Bisect 是二分法的意思,这里使用二分法来排序,它会将一个元素插入到一个有序列表的合适位置,这使得不需要每次调用 sort 的方式维护有序列表。

下面是一个简单的使用示例:

import bisect
import random

random.seed(1)

print'New Pos Contents'
print'--- --- --------'

l = []
for i in range(1, 15):
 r = random.randint(1, 100)
 position = bisect.bisect(l, r)
 bisect.insort(l, r)
 print'%3d %3d' % (r, position), l

输出结果:

New Pos Contents
--- --- --------
 14 0 [14]
 85 1 [14, 85]
 77 1 [14, 77, 85]
 26 1 [14, 26, 77, 85]
 50 2 [14, 26, 50, 77, 85]
 45 2 [14, 26, 45, 50, 77, 85]
 66 4 [14, 26, 45, 50, 66, 77, 85]
 79 6 [14, 26, 45, 50, 66, 77, 79, 85]
 10 0 [10, 14, 26, 45, 50, 66, 77, 79, 85]
 3 0 [3, 10, 14, 26, 45, 50, 66, 77, 79, 85]
 84 9 [3, 10, 14, 26, 45, 50, 66, 77, 79, 84, 85]
 44 4 [3, 10, 14, 26, 44, 45, 50, 66, 77, 79, 84, 85]
 77 9 [3, 10, 14, 26, 44, 45, 50, 66, 77, 77, 79, 84, 85]
 1 0 [1, 3, 10, 14, 26, 44, 45, 50, 66, 77, 77, 79, 84, 85]

Bisect模块提供的函数有:

bisect.bisect_left(a,x, lo=0, hi=len(a)) :

查找在有序列表 a 中插入 x 的index。lo 和 hi 用于指定列表的区间,默认是使用整个列表。如果 x 已经存在,在其左边插入。返回值为 index。

bisect.bisect_right(a,x, lo=0, hi=len(a))

bisect.bisect(a, x,lo=0, hi=len(a))

这2个函数和 bisect_left 类似,但如果 x 已经存在,在其右边插入。

bisect.insort_left(a,x, lo=0, hi=len(a))

在有序列表 a 中插入 x。和 a.insert(bisect.bisect_left(a,x, lo, hi), x) 的效果相同。

bisect.insort_right(a,x, lo=0, hi=len(a))

bisect.insort(a, x,lo=0, hi=len(a)) :

和 insort_left 类似,但如果 x 已经存在,在其右边插入。

Bisect 模块提供的函数可以分两类: bisect* 只用于查找 index, 不进行实际的插入;而 insort* 则用于实际插入。

该模块比较典型的应用是计算分数等级:

def grade(score,breakpoints=[60, 70, 80, 90], grades='FDCBA'):
 i = bisect.bisect(breakpoints, score)
 return grades[i]

print [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]

执行结果:

['F', 'A', 'C', 'C', 'B', 'A', 'A']

同样,我们可以用 bisect 模块实现二分查找:

def binary_search_bisect(lst, x):
 from bisect import bisect_left
 i = bisect_left(lst, x)
 if i != len(lst) and lst[i] == x:
 return i
 return None

我们再来测试一下它与递归和循环实现的二分查找的性能:

Recursion: 4.00940990448
Loop: 2.6583480835
Bisect: 1.74922895432

可以看到其比循环实现略快,比递归实现差不多要快一半。

Python 著名的数据处理库 numpy 也有一个用于二分查找的函数 numpy.searchsorted, 用法与 bisect 基本相同,只不过如果要右边插入时,需要设置参数 side='right',例如:

>>> import numpy as np
>>> from bisect import bisect_left, bisect_right
>>> data = [2, 4, 7, 9]
>>> bisect_left(data, 4)
1
>>> np.searchsorted(data, 4)
1
>>> bisect_right(data, 4)
2
>>> np.searchsorted(data, 4, side='right')
2

那么,我们再来比较一下性能:

In [20]: %timeit -n 100 bisect_left(data, 99999)
100 loops, best of 3: 670 ns per loop

In [21]: %timeit -n 100 np.searchsorted(data, 99999)
100 loops, best of 3: 56.9 ms per loop

In [22]: %timeit -n 100 bisect_left(data, 8888)
100 loops, best of 3: 961 ns per loop

In [23]: %timeit -n 100 np.searchsorted(data, 8888)
100 loops, best of 3: 57.6 ms per loop

In [24]: %timeit -n 100 bisect_left(data, 777777)
100 loops, best of 3: 670 ns per loop

In [25]: %timeit -n 100 np.searchsorted(data, 777777)
100 loops, best of 3: 58.4 ms per loop

可以发现 numpy.searchsorted 效率是很低的,跟 bisect 根本不在一个数量级上。因此 searchsorted 不适合用于搜索普通的数组,但是它用来搜索 numpy.ndarray 是相当快的:

In [30]: data_ndarray = np.arange(0, 1000000)

In [31]: %timeit np.searchsorted(data_ndarray, 99999)
The slowest run took 16.04 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 996 ns per loop

In [32]: %timeit np.searchsorted(data_ndarray, 8888)
The slowest run took 18.22 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 994 ns per loop

In [33]: %timeit np.searchsorted(data_ndarray, 777777)
The slowest run took 31.32 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 990 ns per loop

numpy.searchsorted 可以同时搜索多个值:

>>> np.searchsorted([1,2,3,4,5], 3)
2
>>> np.searchsorted([1,2,3,4,5], 3, side='right')
3
>>> np.searchsorted([1,2,3,4,5], [-10, 10, 2, 3])
array([0, 5, 1, 2])

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者使用python能有一定的帮助,如果有疑问大家可以留言交流。

Python 相关文章推荐
布同 Python中文问题解决方法(总结了多位前人经验,初学者必看)
Mar 13 Python
Django的URLconf中使用缺省视图参数的方法
Jul 18 Python
总结网络IO模型与select模型的Python实例讲解
Jun 27 Python
Python3学习笔记之列表方法示例详解
Oct 06 Python
Python人脸识别第三方库face_recognition接口说明文档
May 03 Python
pycharm创建scrapy项目教程及遇到的坑解析
Aug 15 Python
python urllib爬虫模块使用解析
Sep 05 Python
python爬虫构建代理ip池抓取数据库的示例代码
Sep 22 Python
Python将list元素转存为CSV文件的实现
Nov 16 Python
简述python四种分词工具,盘点哪个更好用?
Apr 13 Python
聊一聊python常用的编程模块
May 14 Python
pandas中pd.groupby()的用法详解
Jun 16 Python
python基础教程之五种数据类型详解
Jan 12 #Python
python实现斐波那契数列的方法示例
Jan 12 #Python
Python爬虫利用cookie实现模拟登陆实例详解
Jan 12 #Python
Python 出现错误TypeError: ‘NoneType’ object is not iterable解决办法
Jan 12 #Python
python获取当前用户的主目录路径方法(推荐)
Jan 12 #Python
Python获取文件所在目录和文件名的方法
Jan 12 #Python
深入理解Python中变量赋值的问题
Jan 12 #Python
You might like
默默简单的写了一个模板引擎
2007/01/02 PHP
php Sql Server连接失败问题及解决办法
2009/08/07 PHP
zf框架的校验器使用使用示例(自定义校验器和校验器链)
2014/03/13 PHP
PHP错误Parse error: syntax error, unexpected end of file in test.php on line 12解决方法
2014/06/23 PHP
PHP定时任务延缓执行的实现
2014/10/08 PHP
Yii2中多表关联查询hasOne hasMany的方法
2017/02/15 PHP
jQuery使用手册之二 DOM操作
2007/03/24 Javascript
走出JavaScript初学困境—js初学
2008/12/29 Javascript
JavaScript异步编程:异步数据收集的具体方法
2013/08/19 Javascript
javascript获取元素CSS样式代码示例
2013/11/28 Javascript
jQuery中:checkbox选择器用法实例
2015/01/03 Javascript
JS实现仿新浪微博发布内容为空时提示功能代码
2015/08/19 Javascript
使用vue.js制作分页组件
2016/06/27 Javascript
解析jQueryEasyUI的使用
2016/11/22 Javascript
JavaScript mixin实现多继承的方法详解
2017/03/30 Javascript
浅谈vue的踩坑路
2017/08/31 Javascript
BootStrap前端框架使用方法详解
2020/02/26 Javascript
JS实现页面鼠标点击出现图片特效
2020/08/19 Javascript
python函数返回多个值的示例方法
2013/12/04 Python
Python数组遍历的简单实现方法小结
2016/04/27 Python
浅谈Python中函数的参数传递
2016/06/21 Python
pandas对dataFrame中某一个列的数据进行处理的方法
2019/07/08 Python
python3 常见解密加密算法实例分析【base64、MD5等】
2019/12/19 Python
使用matplotlib动态刷新指定曲线实例
2020/04/23 Python
pycharm设置默认的UTF-8编码模式的方法详解
2020/06/01 Python
pycharm不以pytest方式运行,想要切换回普通模式运行的操作
2020/09/01 Python
windows系统Tensorflow2.x简单安装记录(图文)
2021/01/18 Python
年终考核评语
2014/01/19 职场文书
婚假请假条格式及范文
2014/04/10 职场文书
团代会宣传工作方案
2014/05/08 职场文书
会计学习心得体会
2014/09/09 职场文书
2014年销售部工作总结
2014/12/01 职场文书
人事行政助理岗位职责
2015/04/11 职场文书
2015年青年教师工作总结
2015/05/25 职场文书
SpringBoot整合Redis入门之缓存数据的方法
2021/11/17 Redis
详解Spring Security如何在权限中使用通配符
2022/06/28 Java/Android