目录
python 二分查找包bisect: python 模块bisect
1、定义
二分查找又称折半查找,它是一种效率较高的查找方法。(要已经排好序!!!)
二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。不妨设有序表是递增有序的。
二分查找只适用顺序存储结构。
2、解题模板
二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间 [l, r] 中, 每次将区间长度缩小一半,当 l = r 时,我们就找到了目标值。
版本1
当我们将区间 [l, r] 划分成 [l, mid] 和 [mid + 1, r] 时,其更新操作是 r = mid 或者 l = mid + 1 ;,计算mid时不需要加1。
C++ 代码模板:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
版本2
当我们将区间 [l, r] 划分成 [l, mid - 1] 和 [mid, r] 时,其更新操作是 r = mid - 1 或者 l = mid ;,此时为了防止死循环,计算mid时需要加1。
C++ 代码模板:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
其实模板1和模板2本质上是根据代码来区分的,而不是应用场景。
如果写完之后发现是l = mid,那么在计算mid时需要加上1,
否则如果写完之后发现是r = mid,那么在计算mid时不能加1。
如何决定check()函数?
假设有一个总区间,经由我们的 check 函数判断后,可分成两部分,
这边以o作 true,.....作 false 示意较好识别如果我们的目标是下面这个v,那么就必须使用模板 1(r = mid)
................vooooooooo
假设经由 check 划分后,整个区间的属性与目标v如下,则我们必须使用模板 2(l = mid)
oooooooov...................
所以下次可以观察 check 属性再与模板1 or 2 互相搭配就不会写错啦
ps: v 的属性,本身也要是 true,也就是 check 为 true 的左边界或是右边界(前提是目标一定是找得到的,不然跳出回圈后,必须加上额外判断)。
来源:AcWing@yxc
3、相关题目
应用版本一:LeetCode 275. H-Index II;
应用版本二:LeetCode 69. Sqrt(x) ;
162. 寻找峰值
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
l = 0; r = len(nums) - 1
while l < r:
mid = l + r >> 1
if nums[mid] < nums[mid + 1]: # 若当前点比右边小,则峰值在右边
l = mid + 1
else:
r = mid
return r
思路:
由于假设左右端点的值为负无穷,所以每次在区间中找出一点,
其到右端点要么单调递减,要么存在峰值
左端点到该点要么单调递增,要么存在峰值
步骤:
我们每次找出区间中间的点,判断它与它右边的数的大小关系(也可比较与左边的数)
若nums[mid] < nums[mid+1]说明存在单调递增,则可以判断Mid右边的区间必存在峰值,
因为只有右边是单调递减才会不存在峰值
每次筛选出的是一定存在峰值的区间,不代表另一半就没有峰值哦
若nums[mid] >= nums[mid+1],不代表右边一定没峰值,只是代表左边一定有峰值
所以为了节省时间快速找到任意一个峰值,我们把右指针 r = mid
LeetCode 162. 寻找峰值-Python-无两段性性质的区间二分 - AcWing
153. 寻找旋转排序数组中的最小值
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7]
可能变为 [4,5,6,7,0,1,2]
)。
# mid < right 表面右边有序 否则左边有序 那么mid > right 则表明最小数肯定在mid 和 right之间
# 套模板
def minArray(self, numbers) -> int:
n = len(numbers)-1
while(n>0 and numbers[n]==numbers[0]): n-=1
# 最后比第一个值大,则一定递增
if numbers[n] >= numbers[0]:return numbers[0]
l, r = 0, n
while(l<r):
mid = (l+r)//2
if numbers[mid]<numbers[0]: r=mid
else:
l = mid+1
return numbers[l]
4、位运算
>> :右移 最高位是0,左边补齐0;最高为是1,左边补齐1
<< :左移 左边最高位丢弃,右边补齐0
>>>:无符号右移 无论最高位是0还是1,左边补齐0
在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方
右移一位相当于除2,右移n位相当于除以2的n次方。
12>>1 结果:6 12/2^1
12>>2 结果:3 12/2^2
12<<1 结果 :24 12x2^1
12<<2 结果 :48 12x2^2
5、其他
优点
二分查找的时间复杂度为O(logn),远远好于顺序查找的O(n)。
平均查找长度ASL
\参考:
backlog
# 要求序列有序
def binary_search(alsit,item):
"""二分查找,递归"""
n = len(alsit)
if n>0:
mid = n//2
if alsit[mid] == item:
return True
elif item<alsit[mid]:
return binary_search(alsit[:mid],item)
else:
return binary_search(alsit[mid+1:],item)
return False
def binary_search_2(alist,item):
"""二分查找,非递归"""
n = len(alist)
first = 0
last = n-1
while first<=last: # 当子表长度大于等于1的时候进行循环!!
mid = (first + last) // 2
if alist[mid]==item:
return True
elif item<alist[mid]:
last=mid-1
else:
first=mid+1
return False