1、插值原理
在上一章中介绍了二分法查找,可以知道二分法是通过对半选择查找位置mid,然而在现实生活中,对于排序均匀有规律的列表,比如字典、电话簿,在查找姓氏“张”的时候,我们都会下意识的从末尾几页开始查找,而不是像二分法那样从中间入手,同理,插值查寻法也是将查找位置mid的对半选择改进为自适应选择(相似三角形定理),从而提高了查找效率;
其中:
- mid:查找位置;
- low:列表的下界值;
- high:列表的上界值;
- searchVal:要查找的值;
2、 插值法特点
1、 有序列表;
2、 列表中的数据分布均匀,逼近线性;
3、 时间复杂度O(logn);
3、图解
3.1、图分析
1、 左边图中数据呈线性递增趋势,右边图中数据呈非线性递增,先凹后凸,有一个拐点;
2、 左图无论查寻值searchVal为多少,只要在[a[low] ,a[high]]之间,就能一次性找到;
3、 在右图中,只要查寻值searchVal产成的两个交点point1和point2未重合,那么就需要通过向左或向右多次循环逼近重合,结束查找;
4、实例代码测试
package xw.zx.algorithm.search;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
public class InterpolationSearch {
private static final Random r=new Random();
private static final int ARRAY_SIZE=1000;
private static Integer[] list=new Integer[ARRAY_SIZE];
public static void main(String[] args) {
InterpolationSearch interpolationSearch=new InterpolationSearch();
//生成数组中有序的元素;
for(int i=0;i<ARRAY_SIZE;i++) {
list[i]=i+1;//interpolationSearch.generatorRand(1,ARRAY_SIZE*10);
}
Arrays.sort(list);
//输出数组长度;
System.out.println("有序数组长度:"+ARRAY_SIZE);
//随机生成需要查询的数值
int searchVal=list[r.nextInt(ARRAY_SIZE)];
int count;
long startTime,endTime;
System.out.println("--------------插值查寻法--------------------");
//开始时间
startTime=new Date().getTime();
//调用插值法查找;
count=interpolationSearch.insertValueSearch(list,searchVal);
//结束时间
endTime=new Date().getTime();
System.out.println("算法运行时间:"+(endTime-startTime)+"ms");
//输出查询值和查询次数
System.out.println("searchVal:"+searchVal+", count:"+count);
System.out.println("--------------二分查寻法--------------------");
//开始时间
startTime=new Date().getTime();
//调用二分法查找;
count=interpolationSearch.binarySearch(list,searchVal);
//结束时间
endTime=new Date().getTime();
System.out.println("算法运行时间:"+(endTime-startTime)+"ms");
//输出查询值和查询次数
System.out.println("searchVal:"+searchVal+", count:"+count);
}
/**
* 随机数生产
* @param start
* @param end
* @return
*/
public int generatorRand(int start,int end) {
int randVal;
randVal=r.nextInt(end-start)+start;
return randVal;
}
/**
* 插值查寻法
* 算法的运行时间为O(logn);
* @param arr
* @param searchVal
* @return
*/
public int insertValueSearch(Integer[] arr,int searchVal) {
//初始化查询次数、下界low、上界high
int count=0,low =0,high=arr.length-1;
int mid,guess,temVal=0;
while(low<=high) {
/**
*1、自适应选择
*2、乘上1.0转为double类型,在进行除运算,可避免整数除以整数后,只保留整数的情况
*3、 mid=low+(searchVal-arr[low])*(high-low)/(arr[high]-arr[low]);这种写法(searchVal-arr[low])*(high-low)计算可能会溢出Integer
*/
mid=low+(int)((searchVal-arr[low])*1.0/(arr[high]-arr[low])*(high-low));
guess=arr[mid];
System.out.println("guess:"+guess);
count++;
//找到值
if(guess==searchVal) {
System.out.println("下标:"+mid );
return count;
}
//定界
if(guess>searchVal) {
high=mid-1;
}else {
low=mid+1;
}
}
return -1;
}
/**
* 二分查寻法
* 算法的运行时间为O(logn);
* @param arr
* @param searchVal
* @return
*/
public int binarySearch(Integer[] arr,int searchVal) {
//初始化查询次数、下界low、上界high
int count=0,low =0,high=arr.length-1;
int mid,guess;
while(low<=high) {
//对半查找
//mid=low+(int)(0.5*(high-low));
mid=(low+high)/2;
guess=arr[mid];
System.out.println("guess:"+guess);
count++;
//找到值
if(guess==searchVal) {
System.out.println("下标:"+mid );
return count;
}
//定界
if(guess>searchVal) {
high=mid-1;
}else {
low=mid+1;
}
}
return -1;
}
}
4.1、线性分析
对于线性递增数据,插值查寻始终能够在一次搜索中,找到查找值searchVal,而二分法则需要循环对半逼近searchVal,所以在这种情况下,插值法效率更高;
4.2非线性分析
在非线性递增的情况下,插值法不再能够每次都一次性找到searchVal,但是跟二分法比较,当被查寻的列表数据量越大时,插值法的查寻次数也会比二分法少;
5、总结
1、插值法采用自适应选择的方式确定查找位置mid;
2、列表中的数据越接近线性递增或递减时,插值法的查找效率越高;