二分查找和插值查找

Posted java从小白到架构师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找和插值查找相关的知识,希望对你有一定的参考价值。

一、概述

在一堆数据里面找出自己需要的元素,这种需求可以说是最基础的需求之一,任何程序都会有所涉及。如果程序的查找算法设计的很精妙,那么整个程序的性能就会有显著的提升。今天就来分享两种常用的查找算法。

二、算法复杂度分析

关于查找最容易想到的一种实现方式就是将一组数据中的每个元素逐个取出来和目标值比较,如果取出来的元素和目标值相同那就说明找到了,这种查找算法叫做顺序查找法,其复杂度为O(N)。二分查找就是:在一组数据中取出中间的那个元素,将其和要找的元素对比,如果不是要找的元素,则以其为标记点,将这组数据分成两半,取出符合条件的一半递归进行如上操作直到找出数据为止。二分查找的复杂度为O(logN),在数据量小的情况下这两种查找算法的效率相差无几,但是在数据量很大的情况下,二分查找的性能就远远高于顺序查找了。下面我们通过一个简单的类比来说明一下两者性能上的差距。

我们现在想让厚度为1毫米的纸来连接地球和月球,通过百度我们查到地球到月球的距离为38.4万千米,如果我们一张一张的叠加纸张,则需要叠加384000000000次才能达到月球,这是一个相当庞大的数据了,但是如果我们用一张纸对折的方法试试呢?结果你们可能想象不到,如果用对折的方式只需要39次就已经超过地月距离了,上述例子中前者一张一张叠加的方式就相当于顺序查找,而后者就相当于二分查找,从以上分析的两个数字中可以很明显的看到两者之间的差距。分析完效率之后,我们开始用代码实现。


三、代码实现

 二分查找的逻辑想必大家也已经清楚了,下面我们就来进行代码实现

   public static int binarySearch(int[] arrays, int aimVal) {        return binarySearch(arrays, 0, arrays.length - 1, aimVal); } private static int binarySearch(int[] arrays, int start, int end, int aimVal) { int mid = (start + end) / 2; int midVal = arrays[mid]; //中间值        //如果开始索引大于终止索引说明找不到该值 if (start > end)  return -1; if (aimVal > midVal) // 向右递归 return binarySearch(arrays, mid + 1, end, aimVal); else if (aimVal < midVal)// 向左递归 return binarySearch(arrays, start, mid - 1, aimVal); else return mid; }

以上代码就简单的实现了二分查找的逻辑思想,没有进行泛型的包装,感兴趣的朋友可以自己尝试着用泛型类将传入的数组包装起来,另外注意:二分查找要求目标数组必须为有序的。否则上述代码就行不通了。如果想让上述代码在数组无序的情况下也可以运作,我这里可以给读者朋友提供一个思想,上周我们讲过分治算法,我们可以利用分治算法来改造上述代码,思想就是将一组数据分为两份,然后让它们分而治之……具体的业务逻辑感兴趣的朋友可以尝试着去实现一下。

插值查找其实和二分查找的思想是相同的。在分析插值查找法之前,我们先用一组数据来说明下二分查找的缺陷。

有一组数据【3,4,6,7,9,13,18,28,37,45,49,50】下面我们想要判断一下这组数据中是否包含3这个元素,如果包含就返回这个元素的index,不包含就返回-1,现在我们用顺序查找法尝试一下,第一个数就是我们需要的值,也即顺序查找只需要执行一次判断就可以得到结果了,那么再看看二分查找法,第一次确定中间数18,第二次确定中间数7,第三次确定中间数4,第四次确定中间数3,刚好是我们需要的值,也即二分查找用了四次才确定结果。针对这一现象,便有了插值查找法。

插值查找法和二分查找法的区别在于所选的标准值不一样,插值查找不像二分查找那样简单的选取中位数来作为标准值,其根据目标元素来确定标准值,下面我们来看代码

 public static int insertSearch(int[] arrays, int aimVal) { return insertSearch(arrays, 0, arrays.length - 1, aimVal); } private static int insertSearch(int[] arrays, int start, int end, int aimVal) {         if (start > end || aimVal < arrays[0] || aimVal > arrays[arrays.length - 1])  return -1; //根据目标值选择标准值 int mid = start + (end - start) * (aimVal - arrays[start]) / (arrays[end] - arrays[start]); int midVal = arrays[mid]; if (aimVal > midVal) // 向右递归 return insertSearch(arrays, mid + 1, end, aimVal); else if (aimVal < midVal) // 向左递归 return insertSearch(arrays, start, mid - 1, aimVal); else return mid;    }
我们观察到上述代码和二分查找有区别的地方在标准值的选取上面,具体代码是:
int mid = start + (end - start) * (aimVal - arrays[start]) / (arrays[end] - arrays[start]);
现在我们将之前的测试数据带进去观察一下,测试数据共有12个元素,我们要找3这个元素,通过计算 mid = 0+(11-0) * (3-3) / (50-3),算出mid=0,最终结果我们用插值查找也同样是一次就能确定结果。

四、小结

本章主要分析了两种基础的查找算法,分析了二分查找和顺序查找的效率,用类比的手法让大家可以直观的感受到在大量数据的情况下二分查找所带来的性能优化。同时介绍了另外一种插值查找法,它解决了在极端情况下二分查找所带来的资源浪费问题。





本人因所学有限,如有错误之处,望请各位指正!

以上是关于二分查找和插值查找的主要内容,如果未能解决你的问题,请参考以下文章

[Algorithm]二分插值斐波那契查找算法 Java 代码实现

算法插值查找算法

插值查找

二分查找和插值查找

查找之二折半查找(二分法查找)和插值查找

二分查找法以及拉格朗日插值查找法