1

作者前言

大家好,我是阿濠,今篇内容跟大家分享的是查找算法之插值查找法,很高兴分享到segmentfault与大家一起学习交流,初次见面请大家多多关照,一起学习进步.

二分查找算法根据折半进行查找,但是还能根据二分查找进行优化吗

假设我们现在有数组arr={1,2,3,4,5,6,7,8,10,11,12},我们使用二分法查找:1

那么根据特性执行会执行几次操作:先是找到中间值二分再进行二分...

那么能不能通过自适应的方案快速定位到需要查找的值呢

那么就可以使用插值查找算法

一、插值查找法的介绍

插值查找原理介绍:

1.插值查找算法类似于二分查找不同的是插值查找每次从自适应mid处开始查找。

2.将折半查找中求mid索引的公式进行优化key 代表查找的值findValue

clipboard.png

公式为:int midIndex = low + (high - low) * (key - arr[low]) / (arr[high]- arr[low]);

对应代码:int mid= left+(right-left) *(findvalue -arr[left])/ (arr[right]-arr[left])

二、算法思路解析

二分查找法为什么是 left + right / 2 呢?

因为二分法查找的思想基于数组的有序性,每次都将当前的数组分为两半,通过关键字和中间元素的比较,立即排除掉其中不可能存在和键值相等的元素的那一半

所以使用通过 left + right / 2,并比较Keyarr[mid]大小,丢掉"一半的元素"

(从 left + right / 2 可以看出被排除的一半元素不会纳入到下一次的比较中了)

插值查找为什么是(findvalue-arr[left])/(arr[right]-arr[left])

首先我们根据基于数组的有序性,想一想查找的位置一定要从中间开始查找吗

打个比方:我们在一本英文字典里查找apple这个单词的时候, 你肯定不会从字典中间开始查找, 而是从字典开头部分开始翻,因为觉得这样的找法才是比较快的。

那么为什么你会从头部分开始翻呢?因为知道字典按照字母有序排序的,并且apple这个单词是a开头,所以从头开始翻,对嘛?

那么这时就有了一个思路: 如果能在查找前准确地预测关键字在数组中的位置的话,这样的查找方法能比二分查找提高更多的性能

所以根据差值公式( key - arr[low]) / (arr[high] -arr[low]),将要查找的关键字 key 与查找表中的最大、最小记录的关键字比较的查找方法。

三、通过应用示例认识插值查找算法

举例:有一数组arr=[1,2,3,4,5,6,7,8......100] 假设我们需要查找的值为:1

按照二分法查找:需要 mid = (left + right ) / 2折半...再折半...才找到1

按照插值查找算法

int mid = left+(right-left) \* (findvalue -arr[left]) / (arr[right]-arr[left])

对应的代码:int mid= 0+(99-0) * (1- 1) / (100-1) = 0+99 * 0 / 99 = 0 直接找到值:1

比如我们查找的值为:100

对应的代码:int mid=0 + (99-0) * (100-1) /(100-1)= 0+99 * 99/99 = 0+99=99

/**
 * @param arr 数组
 * @param left 左边索引
 * @param right 右边索引
 * @param  findValue 查找值
 * @return 如果找到则返回对应的下标,没有找到返回-1 即可
 */
//编写插值查找方法
public static  int insertValueSearch(int[] arr,int left,int right,int findValue){
    //一、没有找到的情况或者查找的值(小于数组最小值或者大于数组最大值)
    if(left>right||findValue<arr[0]||findValue>arr[arr.length-1]){
        return -1;
    }

    //找出预测数组中的位置
    int mid=left+(right - left) * (findValue - arr[left]) / (arr[right]-arr[left]);
    //对应的预测值
    int midValue=arr[mid];

    //若查找的值比定位的值大,则需要向右递归
    if(findValue>midValue){
        //若`findVale>arr[mid]`,则说明查找的数`findValue在右边`,进行递归`向右查询`
        return insertValueSearch(arr,mid+1,right,findValue);
    }else if(findValue<midValue){
        //若`findVale<arr[mid]`,则说明查找的数`findValue在左边`,进行递归`向左查询`
        return  insertValueSearch(arr,left,mid -1,findValue);
    }else{
        //若`findVale = =arr[mid]`,则说明`查找的数已找到并返回下标`
        return mid;
    }
}
int[] arr = new int[100];
for (int i = 0; i < 100; i++) {
    arr[i] = i + 1;
}
/**
 * 在元素数值均匀分布的有序数组里面, 用这种方法查找是很快的。
 * 特别的,对绝对均匀分布的数组(相邻元素差值相同), 插值查找用一次比较就能查找成功
 * 当然了,前提是数组中元素数值是均匀分布的
 * 如果是对 1,2,40,99,1000这种分布很不均匀的数组, 插值查找的计算会起到反效果, 就不如二分查找了
 */
int index=insertValueSearch(arr,0,arr.length-1,100);
System.out.println("查找的值对应下标是:"+index);

运行结果如下:
查找的值对应下标是:99

四、插值查找注意事项

在元素数值均匀分布的有序数组里面, 用这种方法查找是很快的。特别的,对绝对均匀分布的数组(相邻元素差值相同), 插值查找用一次比较就能查找成功:

当然了, 前提是数组中元素数值是均匀分布的, 如果是对 1,2,40,99,1000 这种分布很不均匀的数组, 插值查找的计算会起到反效果, 就不如二分查找了

五、算法复杂度分析

时间复杂度平均为:O(logn)


28640
116 声望25 粉丝

心有多大,舞台就有多大