数据结构与算法学习笔记之二分搜索
二分搜索的优缺点
二分搜索(折半搜索)的Wikipedia定义:是一种在有序数组中查找某一特定元素的搜索算法。从定义可知,运用二分搜索的前提是数组必须是排好序的。另外,输入并不一定是数组,也有可能是给定一个区间的起始和终止的位置。
优点:搜索速度快,时间复杂度为O(logn),即对数时间,也称为对数查找。
缺点:要求待查找的数组或者区间是排好序的。对数组进行动态的删除和插入操作并完成查找,平均复杂度会变为O(n)。此时应当考虑采取自平衡的二叉查找树:
- 在 O(nlogn) 的时间内用给定的数据构建出一棵二叉查找树;
- 在 O(logn) 的时间里对目标数据进行搜索;
- 在O(logn) 的时间里完成删除和插入的操作。
因此,当输入的数组或者区间是排好序的,同时又不会经常变动,而要求从里面找出一个满足条件的元素的时候,二分搜索就是最好的选择。
二分搜索的实现方法
递归解法
优点:简洁;缺点:执行消耗大
def binarySearch(list,target):
low = 0
high = len(list)-1
midindex = int(low+(high-low)/2)
if target==list[midindex] :
return list[midindex]
elif target < list[midindex]:
return binarySearch(list[:midindex-1],target)
elif target > list[midindex]:
return binarySearch(list[midindex+1:],target)
return -1
非递归写法
def binarySearch2(list,target):
low = 0
high = len(list) - 1
while low <= high :
mid = int((low+high) /2 )
if target == list[mid]:
return mid
elif target<list[mid]:
high = mid-1
elif target > list[mid]:
low = mid+1
return -1
二分搜索的面试题种类及解题思路
- 找确定的边界问题
边界分上边界和下边界,有时候也被成为右边界和左边界。确定的边界指边界的数值等于要找的目标数。
解题思路:关键点在于需要确定边界的条件。在本题中,如示例1所示,左边界的条件为,当前的值等于8,并且左边的数值比当前小(list[n-1]<list[n]),或者左边没有数即当前为第一个数。有边界条件同理
def searchRange(list,target):
left_index= searchLeft(list,target)
right_index = searchRight(list,target)
if left_index == None or right_index==None:
return [-1,1]
return left_index,right_index
# 寻找下边界
def searchLeft(list,target):
low = 0
high = len(list)-1
while low <= high:
mid = int((low+high)/2)
if target == list[mid] and (mid==0 or list[mid-1]<list[mid]):
return mid
elif target <= list[mid]:
high = mid-1
else:
low = mid +1
def searchRight(list,target):
low = 0
high = len(list)-1
while low <= high:
mid = int((low+high)/2)
if target == list[mid]:
if list[mid+1]==None or list[mid]<list[mid+1]:
return mid
elif target <= list[mid]:
high = mid - 1
else:
low = mid + 1
- 找迷糊的边界
二分搜索可以用来查找一些模糊的边界。模糊的边界指,边界的值并不等于目标的值,而是大于或者小于目标的值。
例题:从数组 {-2, 0, 1, 4, 7, 9, 10} 中找到第一个大于 6 的数。
解题思路
边界条件:
1.该数一定要大于6
2.该数为第一个数,或者它之前的数比6小
def search(list,target):
low= 0
high = len(list)-1
while low<=high:
mid = (low+high)//2
if target<list[mid] and (mid==0 or list[mid-1]<=target):#<=是为了解决6,7 这种情况
return list[mid],mid
if target<list[mid]:
high=mid-1
else:
low = mid+1
- 旋转的排序数组
解题思路:关键于,判断一边是否为排序好的数组
def Search_best(list,target):
low = 0
high = len(list) - 1
while low <= high:
mid = int((low + high) / 2)
if target == list[mid]:
return mid
# 右边是完整的排序数列
if list[mid] < list[high]:
if list[mid]< target<= list[high]:
low = mid+1
else:
high = mid -1
else:
if list[low]<= target < list[mid]:
high=mid -1
else:
low = mid +1
return -1
- 不定长的边界
例题:有一段不知道具体长度的日志文件,里面记录了每次登录的时间戳,已知日志是按顺序从头到尾记录的,没有记录日志的地方为空,要求当前日志的长度。
解题思路
可以把这个问题看成是不知道长度的数组,数组从头开始记录都是时间戳,到了某个位置就成为了空:{2019-01-14,2019-01-17,…,2019-08-04,….,null,null…}
//先通过getUpperBound函数不断地去试探在什么位置会出现空的日志。
intgetUpperBound(String[]logs,inthigh){
if(logs[high]==null){
return high;
}
return getUpperBound(logs,high*2);
}
//再使用二分搜索
int binarySearch(String[] logs, int low, int high) {
if (low > high) {
return -1;
}
int middle = low + (high - low) / 2;
if (logs[middle] == null && logs[middle - 1] != null) {
return middle;
}
if (logs[middle] == null) {
return binarySearch(logs, low, middle - 1);
} else {
return binarySearch(logs, middle + 1, high);
}
}