作者前言
大家好,我是阿濠,今篇内容跟大家分享的是查找算法之插值查找法,很高兴分享到segmentfault与大家一起学习交流,初次见面请大家多多关照,一起学习进步.
二分查找算法
根据折半进行查找
,但是还能根据二分查找
进行优化吗
?
假设我们现在有数组arr={1,2,3,4,5,6,7,8,10,11,12}
,我们使用二分法查找:1
那么根据特性
执行会执行几次操作
:先是找到中间值
、二分
、再进行二分
...
那么能不能通过自适应的方案
,快速定位
到需要查找的值呢
?
那么就可以使用插值查找算法
一、插值查找法的介绍
插值查找原理介绍:
1.插值查找算法类似于二分查找
,不同
的是插值查找
每次从自适应mid处开始
查找。
2.将折半查找中
的求mid索引的公式
进行优化
,key
代表查找的值findValue
公式为: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
,并比较Key
和arr[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)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。