二分法
不知道大家有没有玩过一个猜数字的游戏?玩家一给出一个区间和一个数字,由玩家二来猜这个数字,区间会随着玩家二猜的数字发生变化。
在不考虑表情变化或者别的影响因素时,玩家二要如何去猜这个数字呢?
下面我们要介绍的,就是这个游戏的万能解法----二分法,也叫做二分查找法
定义
二分查找(英语:binary search),也称折半搜索(英语:half-interval search)、对数搜索(英语:logarithmic search),是用来在一个有序数组中查找某一元素的算法。
过程
以在一个升序数组中查找一个数为例。
它每次考察数组当前部分的中间元素,如果中间元素刚好是要找的,就结束搜索过程;如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找;如果中间元素大于所查找的值同理,只需到左侧查找。
关键元素
-
排好序的列表list
-
左区间left
-
右区间right
-
中间点mid
-
目标值target
性质
时间复杂度
二分查找的最优时间复杂度为 O(1)。
二分查找的平均时间复杂度和最坏时间复杂度均为O(logn) 。因为在二分搜索过程中,算法每次都把查询的区间减半,所以对于一个长度为n的数组,至多会进行O(logn)次查找。
空间复杂度
迭代版本的二分查找的空间复杂度为O(1) 。
递归(无尾调用消除)版本的二分查找的空间复杂度为O(logn) 。
Tips
二分法的演变过程如下图所示
通过不断的修正区间,达到找目标值的目的。
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def Bisection(tar): weight=[1<<i for i in range(1,10)] print("Weight is ",weight) l,r=0,len(weight)-1 while l<=r: mid=(r-l)//2+l if weight[mid]==tar: return mid if weight[mid]<tar: l=mid+1 else: r=mid-1 return -1
|
我们来看看输出情况
1 2 3 4 5
| print(Bisection(512)) print(Bisection(127)) print(Bisection(128)) print(Bisection(0)) print(Bisection(513))
|
1 2 3 4 5 6 7 8 9 10
| Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 8 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] -1 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 6 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] -1 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] -1
|
可以发现,目标值都被正确的输出了,而不在范围内的值,则是以-1进行输出。
二分插入
向右边插入
这个问题的关键在于,当tar==mid的时候,我们让左区间往右走。于是,当出现相同值的时候,插入位置被修正为右边。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def Bisection(tar): weight=[1<<i for i in range(1,10)] print("Weight is ",weight) l,r=0,len(weight)-1
while l<=r: mid=(r-l)//2+l if weight[mid]<=tar: l=mid+1 else: r=mid-1 return l
|
1 2 3 4 5 6 7 8 9 10
| Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 9 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 6 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 7 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 0 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 9
|
诶,我们发现,512的插入位置在9号,128的插入位置在7号。
向左插入
同理啦,我们只需要在tar==n[mid]的时候,把右区间往左移,从而实现拿到左端点的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def Bisection(tar): weight=[1<<i for i in range(1,10)] print("Weight is ",weight) l,r=0,len(weight)-1
while l<=r: mid=(r-l)//2+l if weight[mid]>=tar: r=mid-1 else: l=mid+1 return l
|
1 2 3 4 5 6 7 8 9 10
| Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 8 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 6 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 6 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 0 Weight is [2, 4, 8, 16, 32, 64, 128, 256, 512] 9
|
可以看到,512被插入到了8号位,513则被插入到了9号位。