查字典
当我们从字典中查找 “algorithm” 这个单词的时候,我们肯定不会傻傻地像二分查找一样首先从中间开始。相反,我们会从首字母为 a 的地方开始查找,然后根据第二个字母在字母表中的位置,找到相应的位置再继续查找,这样重复这个过程,直到我们查找到这个单词。
接下来我们就来介绍一下类似于上述过程的插值查找。
插值查找
插值查找(interpolation search)实际上是二分查找的改良版。假设有这样一个数组
,我们可以发现,每个相邻元素的差均为
,满足
均匀分布。如果要查找元素
,我们首先可以计算数组中小于等于 70 的元素占所有元素的比例的期望值
,而数组的长度
我们知道等于
,所以我们期望查找的索引值就为
,对应的元素为
,恰好就是我们要找的元素。这样,原本用二分法需要查找 3 次的用插值查找只用查找 1 次,大大提高了查找的效率。
这里,我们用一个公式来表示每次查找的期望索引值:
其中,l
和 r
分别代表数组的第一个和最后一个索引,key
代表待查找的元素。
跟二分查找一样,如果一次查找失败,数组的长度就相应地减小,再代入上面的公式继续查找,直到查找成功或失败。
def formula(l, r, key, array):
p = (key - array[l]) / (array[r] - array[l])
n = r - l
idx = int(n * p)
return idx
def interpolation_search(array, key):
l = 0; r = len(array) - 1
while l <= r:
m = l + formula(l, r, key, array)
if array[m] == key:
return m # 查找成功
elif array[m] < key:
l = m + 1 # 查找右侧的数组
else:
r = m - 1 # 查找左侧的数组
return -1 # 查找失败
复杂度分析
插值查找的平均复杂度为
,但证明过程相当的复杂,这篇
论文给出了详细的证明过程,感兴趣的同学可以自己去看看,这里我们就不再讨论了。
要是数组不是均匀分布的,插值查找的复杂度会退化到线性的复杂度
。举一个极端的例子,假设数组为
,我们要查找元素
。第一轮查找我们计算出索引值为
,第二轮为
,第三轮为
,这样我们查找了三次。推广到含有
个元素的数组就需要查找
次,所以复杂度就为
。
因此,插值查找的高效性只针对均匀分布的数组,而对于分布不均匀的数组,插值查找便不再适用了。
→ 本节全部代码 ←
← 二分查找与二叉树 | 算法与复杂度zhuanlan.zhihu.com