二分查找和二叉树的中序遍历的底层原理是一致的,只不过考察侧重点不一样
查找概述
查找可以很简单,也可以很复杂,散列、动态规划等高难度算法都可以视为查找问题;
常见的查找算法有顺序查找、二分查找、插值查找、斐波那契查找、树表查找、分块查找、哈希查找等;
二分查找、插值查找、斐波那契查找可以归为一类——插值查找。插值查找和斐波那契查找是在二分查找的基础上的优化查找算法;
重要:哈希查找,二分查找
凡是涉及到在排好序的地方的查找,都可以考虑二分来优化查找效率。不一定全局都排好才行,只要某个部分是排好的,就可以针对该部分进行二分查找,这是优化查找的重要途径。
二分查找的进一步拓展,二分每次取一半,但在某些场景下,大致知道数据的位置了,不必折半,取1/3、1/4这样也可以。
插值查找的通用公式:
mid = low + (key-a[low])/(a[high]-a[low])*(high-low)
注:公式没看懂
分块查找
分块查找是折半(二分)查找和顺序查找的一种改进方法;
只要求索引表是有序的,对块内节点没有排序要求,特别适合于节点动态变化的情况;
分块查找要求把一个数据分为若干块,每一块里面的元素可以是无序的,但是块与块之间的元素需要是有序的。
举例,即第1块中任一元素的关键字必须小于第2块中任一元素的关键字,第2块中任一元素的关键字必须小于第3块中任一元素的关键字,依次类推。
最简单,有序无序都可以,不需要排序,一个个对比,效率低下。
def search(arr, key):
for i in range(len(arr)):
if arr[i] == key:
return i
return -1
注:二分查找、插值查找、斐波那契查找都是基于已经排序过的数据
分治法/分治思想
即分而治之,把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题…直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
注:分治是很多高效算法的基础,如二分查找、排序算法(快速排序、归并排序)等
二分查找
二分查找就是将中间结果与目标进行比较,一次去掉一半。二分查找可以用递归或者循环的方式来做。
注:最简单、最典型的分治
代码实现
def binary_search(array, low, high, target):
while low <= high:
mid = (low + high) // 2
if array[mid] == target:
return mid
elif array[mid] > target:
high = mid - 1
else:
low = mid + 1
return -1
if __name__ == '__main__':
print(binary_search([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0, 9, 8))
print(binary_search([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0, 9, 20))
注:
如果用Java实现,存在一个细节改进(python语言里没有该问题)
原始:int mid = (low+high)/2
改进1:int mid = (low+high)>>1 说明:除的效率低,移位代替
改进2:int mid = low+((high-low)>>1) 说明:low+high可能会溢出
代码实现
def binary_search(array, low, high, target):
if low > high:
return -1
mid = (low + high) // 2
if array[mid] == target:
return mid
elif array[mid] > target:
return binary_search(array, low, mid - 1, target)
else:
return binary_search(array, mid + 1, high, target)
if __name__ == '__main__':
print(binary_search([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0, 9, 8))
print(binary_search([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0, 9, 20))
在上面的基础上,元素存在重复,如果重复则找左侧第一个
关键:找到目标结果之后不是返回而是继续向左侧移动。
方法1:找到相等位置向左使用线性查找,直到找到对应的位置
方法2:加入重复的数量特别大,考虑使用二分。找到目标元素之后根据要求继续递归寻找
# 方法1
def binary_search(array, low, high, target):
if low > high:
return -1
mid = (low + high) // 2
if array[mid] == target:
# 若存在重复元素,左移,寻找最左边的元素
if mid == 0:
return mid
else:
while mid >= 0 and array[mid] == target:
mid -= 1
return mid + 1
elif array[mid] > target:
return binary_search(array, low, mid - 1, target)
else:
return binary_search(array, mid + 1, high, target)
if __name__ == '__main__':
print(binary_search([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0, 9, 7))
print(binary_search([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0, 9, 20))
print(binary_search([0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 8, 9], 0, 11, 7))
# 方法1优化
def binary_search(array, low, high, target):
if low > high:
return -1
mid = (low + high) // 2
if array[mid] == target:
# 若存在重复元素,左移,寻找最左边的元素
while mid >= 0 and array[mid] == target:
mid -= 1
return mid + 1
elif array[mid] > target:
return binary_search(array, low, mid - 1, target)
else:
return binary_search(array, mid + 1, high, target)
方法2:二分查找
"""
题目:
有重复元素的二分查找,如果有重复元素则找左侧第一个
"""
def binary_search(nums, target):
if not nums:
return -1
left = 0
right = len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1
elif nums[mid] == target:
right = mid
else:
right = mid - 1
return right if nums[right] == target else -1
if __name__ == '__main__':
print(binary_search([], 4)) # -1
print(binary_search([0, 1, 2, 3], 10)) # -1
print(binary_search([0, 1, 2, 3], -10)) # -1
print(binary_search([0, 1, 2, 3, 4, 5], 4)) # 4
print(binary_search([0, 1, 2, 3, 3, 4, 5], 3)) # 3
print(binary_search([0, 1, 2, 3, 3, 3, 4, 5], 3)) # 3
print(binary_search([0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5], 3)) # 3