插值查找(Interpolation Search)


原文链接:https://zhuanlan.zhihu.com/p/133535431


查字典

当我们从字典中查找 “algorithm” 这个单词的时候,我们肯定不会傻傻地像二分查找一样首先从中间开始。相反,我们会从首字母为 a 的地方开始查找,然后根据第二个字母在字母表中的位置,找到相应的位置再继续查找,这样重复这个过程,直到我们查找到这个单词。

接下来我们就来介绍一下类似于上述过程的插值查找。

插值查找

插值查找interpolation search)实际上是二分查找的改良版。假设有这样一个数组  [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] ,我们可以发现,每个相邻元素的差均为 10 ,满足均匀分布。如果要查找元素 70 ,我们首先可以计算数组中小于等于 70 的元素占所有元素的比例的期望值 p = \frac{(70 - 0)}{(90 - 0)} = \frac{7}{9} ,而数组的长度 n 我们知道等于 10 ,所以我们期望查找的索引值就为 ⌊n × p⌋ = 7 ,对应的元素为 70 ,恰好就是我们要找的元素。这样,原本用二分法需要查找 3 次的用插值查找只用查找 1 次,大大提高了查找的效率。

这里,我们用一个公式来表示每次查找的期望索引值:

x=⌊\frac{(key-A[l])(r-l)}{A[r]-A[l]}⌋

其中,lr 分别代表数组的第一个和最后一个索引,key代表待查找的元素。

跟二分查找一样,如果一次查找失败,数组的长度就相应地减小,再代入上面的公式继续查找,直到查找成功或失败。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace std;
int formula(int l, int r, int key, int* num)
{
    return (r - l) * (key - num[l]) / (num[r] - num[l]);
}

int interpolationSearch(int* num, int key, int n)
{
    int l = 0;
    int r = n - 1;
    int x;
    while(l < r) {
        x = l + formula(l, r, key, num);
        x = max(l, min(x, r));  //不能超过数组的右边界
        if(num[x] == key)
            return x;
        else if(num[x] < key)
            l = x + 1;
        else
            r = x - 1;
    }
    return -1;
}

int main(int argc, char* argv[])
{
    int num[11] = {-10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90};
    int key = 70;  //要查找的值
    int index = -1;  //要查找的值得索引,初始化为-1
    index = interpolationSearch(num, key, 11);

    if(index != -1)
        cout << index << endl;
    else
        cout << "No Such Number!" << endl;

    return 0;
}

复杂度分析

插值查找的平均复杂度为 Θ(\log \log n) ,但证明过程相当的复杂,这篇论文给出了详细的证明过程,感兴趣的同学可以自己去看看,这里我们就不再讨论了。

要是数组不是均匀分布的,插值查找的复杂度会退化到线性的复杂度 Θ(n) 。举一个极端的例子,假设数组为 [0, 99, 100, 100, 100] ,我们要查找元素 99 。第一轮查找我们计算出索引值为 3,第二轮为 2,第三轮为 1,这样我们查找了三次。推广到含有 n 个元素的数组就需要查找 n - 2 次,所以复杂度就为 Θ(n)

因此,插值查找的高效性只针对均匀分布的数组,而对于分布不均匀的数组,插值查找便不再适用了。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
插值查找是一种基于二分查找的改进算法,适用于有序数组中均匀分布的情况。它的核心思想是根据查找值在数组中的相对位置,计算出一个比二分查找更接近查找值的位置,从而提高查找效率。 下面是Python实现插值查找的代码: ```python def interpolation_search(arr, target): """ 插值查找算法 :param arr: 有序数组 :param target: 查找目标值 :return: 目标值在数组中的索引,不存在返回-1 """ n = len(arr) low, high = 0, n - 1 while low <= high: pos = low + (target - arr[low]) * (high - low) // (arr[high] - arr[low]) # 计算插值位置 if pos < 0 or pos >= n: # 超出数组范围,退出循环 break if arr[pos] == target: return pos elif arr[pos] < target: low = pos + 1 else: high = pos - 1 return -1 ``` 其中,插值位置的计算公式为: ``` pos = low + (target - arr[low]) * (high - low) // (arr[high] - arr[low]) ``` 这里将插值位置看作是查找值在数组中所占的比例,乘以数组的长度后加上起始位置,即可得到插值位置。这个计算公式比较容易理解,但需要注意的是,如果数组中存在重复元素,那么插值查找可能会出现死循环,因为插值位置可能会一直在同一个位置上循环。 在使用插值查找时,需要注意以下几点: - 适用于均匀分布的有序数组,如果分布不均匀,可能会导致效率降低; - 插值查找需要使用除法运算,可能会降低效率; - 如果数组中存在重复元素,可能会出现死循环。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值