二分查找法
1. 一般二分查找法
二分查找适用查找序列:对于有序序列的查找
二分查找法的时间复杂度为O(logN)
二分查找的主要思路是:对于有序序列(假设为非递减序列),每次找到左右区间的中间位置,比较查找目标元素与中间值的大小,如果目标元素值小于中间值,则向左缩减查找的区间,否则向右缩减区间值。
对于给定闭区间[a, b], left= a, right = b, mid = left + int((right - left) / 2) ,此处使用减法操作是为了防止溢出。
(1)input_arr[mid] > target_num: right = mid -1
(2)input_arr[mid] < target_num: left = mid + 1
(3)input_arr[mid] = target_num: return mid;
没有找到目标元素值,则返回-1.
python实现:
# 一般二分查找
def binary_search(input_list, target):
if len(input_list) == 0:
return -1
left = 0
right = len(input_list) - 1
while left <= right:
# 一般思路为:mid = (left + right) / 2, 但是该计算方法可能造成加法溢出
mid = int(left + (right - left) / 2)
if input_list[mid] == target:
return mid
elif input_list[mid] > target:
right = mid - 1
else:
left = mid + 1
return -1
递归实现二分查找法
递归在性能上会略差,但是其性能差异是常数级别
# 用递归的方法实现二分查找法
def r_binary_search(input_arr, left, right, target):
mid = left + int((right - left) / 2)
if input_arr[mid] == target:
return mid
if input_arr[mid] < target:
left = mid + 1
else:
right = mid - 1
r_binary_search(input_arr, left, right, target)
2. 带有重复元素的二分查找法,获取最左侧的元素index
# 带有重复元素的二分查找,获取最左侧的index值
def repeat_binary_search(input_list, target):
if len(input_list) == 0:
return -1
left = 0
right = len(input_list) - 1
# 如果大于或等于目标值则right左移动到mid位置,小于目标值则left右移动到mid+1位置
# 在input_list[mid] >= target的情况下,目标解的位置为[left, mid]闭区间
# 循环终止的条件为:left=right
while left < right:
mid = int(left + (right - left) / 2)
if input_list[mid] >= target:
right = mid
else:
left = mid + 1
return left
实现floor()/ceil()函数
floor():对于重复元素的查找
如果找到target, 返回第一个target相应的索引index
如果没有找到target, 返回比target小的最大值相应的索引, 如果这个最大值有多个, 返回最大索引
如果这个target比整个数组的最小元素值还要小, 则不存在这个target的floor值, 返回-1
# 对于重复元素的查找
# 如果找到target, 返回第一个target相应的索引index
# 如果没有找到target, 返回比target小的最大值相应的索引, 如果这个最大值有多个, 返回最大索引
# 如果这个target比整个数组的最小元素值还要小, 则不存在这个target的floor值, 返回-1
def floor(input_arr, target):
if target < input_arr[0]:
return -1
# 查找目标元素的区间为[left, right],此处为左闭右闭
left = 0
right = len(input_arr) - 1
while left < right:
# 使用向上取整避免死循环
mid = left + int((right - left + 1) / 2)
# 需要返回第一个target的索引值,所以需要右边值区间往左移动
if input_arr[mid] < target:
left = mid
else:
right = mid - 1
if left < len(input_arr) - 1 and input_arr[left + 1] == target:
return left + 1
return left
ceil()函数:二分查找法, 在有序数组arr中, 查找target
如果找到target, 返回最后一个target相应的索引index
如果没有找到target, 返回比target大的最小值相应的索引, 如果这个最小值有多个, 返回最小的索引
如果这个target比整个数组的最大元素值还要大, 则不存在这个target的ceil值, 返回整个数组元素个数n
# 二分查找法, 在有序数组arr中, 查找target
# 如果找到target, 返回最后一个target相应的索引index
# 如果没有找到target, 返回比target大的最小值相应的索引, 如果这个最小值有多个, 返回最小的索引
# 如果这个target比整个数组的最大元素值还要大, 则不存在这个target的ceil值, 返回整个数组元素个数n
def ceil(input_arr, target):
if target > input_arr[-1]:
return len(input_arr)
left = 0
right = len(input_arr) - 1
while left < right:
# 使用向下取整避免死循环
mid = left + int((right - left - 1) / 2)
# 需要返回最后一个target的索引值,所以需要左边值区间往右移动
if input_arr[mid] <= target:
left = mid + 1
else:
right = mid
if right >= 0 and input_arr[right - 1] == target:
return right - 1
return right
3. 求元素开平方
大于1的元素开平方,要限制元素的范围是大于等于1,对小于1的元素左右区间设置不一致
# 求开方: x开方值应该在(0, x]之间
def sqrt_binary_search(target_num):
try:
assert target_num >= 1
left = 0
right = target_num
# 目标区间为左开右闭 (left, right]
while left < right:
mid = left + (right - left) / 2
divide_ = target_num / mid
if abs(divide_ - mid) < 0.00001:
return mid
if divide_ <= mid:
right = mid
else:
left = mid
return -1
except AssertionError:
print("输入开根号的数据不符合规范")
return None
4. 求大于给定元素的最小元素
# 求大于给定元素的最小元素, 找到返回元素值,否则返回第一个元素值
def next_smallest(input_list, target):
input_length = len(input_list)
assert input_length >= 1
left = 0
right = len(input_list) - 1
# 对于有重复元素的列表仍然适用
while left <= right:
mid = left + int((right - left) / 2)
if input_list[mid] <= target:
left = mid + 1
else:
right = mid - 1
return input_list[left] if left < input_length else input_list[0]
5. 查找区间
# 查找区间:返回一个元素(可以重复)在数组中的位置区间
def find_element_span(input_list, target):
first_index = repeat_binary_search(input_list, target)
if input_list[-1] == target:
last_index = len(input_list) - 1
else:
last_index = repeat_binary_search(input_list, target + 1) - 1
if first_index == len(input_list) or input_list[first_index] != target:
return [-1, -1]
else:
return [first_index, last_index]
每天进步一点点…
上述代码均通过小样本测试用例,如若有误还望指正。