天天看點

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

插值查找算法

  • 引言
  • 什麼是內插補點查找算法
    • 優化内容
    • 內插補點查找思路
    • 公式分析
    • 內插補點查找算法的局限
  • 代碼實作

引言

說是插值查找算法,我覺得叫不如叫內插補點查找算法更加貼切。因為是根據相關資料的內插補點來确定查找範圍的。本文的後續内容我就以內插補點查找算法,代替插值查找算法了。

什麼是內插補點查找算法

問到內插補點查找算法,那麼你就應該已經知道了什麼是二分查找算法了。

(什麼?你還不知道?點這裡了解二分查找算法)

簡單一點來說,內插補點查找算法是對二分查找算法的做了一些小小的優化。

優化内容

取中點值的公式不同。

在二分查找算法裡我們取mid的公式如下

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

為了後續的推導我們變換一下

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

但是在內插補點查找算法裡我們取mid的公式如下:

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

這就是兩種算法的差別所在。是以了解插值排序,隻需要看懂這個公式即可,接下來我們來分析這個公式吧。

內插補點查找思路

在講解這個公式之前,我們需要先知道內插補點查找的思路是什麼。

二分查找的思路是:直接分一半,然後每次處理一半。但是假如我們找的值在最左邊,或者最右邊,那麼就必須一次一次分割到最後才行了。

那麼有沒有可能我可以把查找範圍縮到更小,縮小到上次數組的1/3、1/4或者更小?這就是內插補點查找算法要做的事情,把查找的範圍縮的得更小。

公式分析

那麼是怎樣實作的呢?我們觀察兩個取mid的公式

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作
Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

差別就在于第一個參數,二分查找取值是1/2

而內插補點查找是

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

那麼我們來看這些變量的含義:

target : 查找的目标值

arr[left ] : 需要查找數組的左邊元素的值

arr[right] : 需要查找數組的右邊元素的值

是以當以上這個式子的值小于1/2時,那麼所取得的mid也更小,中點就越靠近左端的left。

那麼如果目标值在正好在mid左側那我們是不是就縮小了下一次查找的範圍?

請注意上面這段話

我們注意這樣的一種情況:當數組呈均勻分布時(比如0,2,4,6,8,16…這種),內插補點查找算法的算法需要幾次找到呢?我們看以下式子推導

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

假設最左側的值為 L

數組共有n個元素

相鄰兩數的內插補點為k

所求target 的下标為i

那麼target = L + i*k,

arr[ right ] - arr[ left ] = (n - 1) * k ,

right - left = n - 1。

代入上述式子就變成了

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

約分變換之後,你就得到

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

因為二分查找的起始位置是從0開始的 ,是以第一次查找的時候 得到 mid = i就是直接找到需要尋找的值的下标。

是以當數組呈均勻分布時,內插補點查找隻需要找一次即可找到所需要的結果。

內插補點查找算法的局限

看了上述的推導,是不是覺得內插補點排序很厲害。但是還記得我讓你注意的那句話嗎?

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

雖然我們可以縮小某一側的範圍,但是我們并不能保證所尋找資料就在那一側。也就是說,我按mid把數組分為:左側占原數組1/3 , 右側占2/3,但是我所需要找到的資料在右側。那麼此時我需要查找的範圍時2/3 比 二分查找的1/2更大,所需要查找的資料更多。

比如這樣的一個數組[0 , 1, 5 , 7, 8 , 12, 100],我們的 target = 8。按照上述公式

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

得到的mid為 0.48 向下取整為0,下一次查找的範圍為[ 1 , 6]

比二分查找的得到的範圍[ 4 , 6]更大。

在上面的情況下,內插補點查找算法隻能從頭到尾依次查找了,退化成順序查找算法。

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作

是以內插補點排序并很不是穩定的。但如果能确定數組呈均勻分布或者相鄰值的內插補點并不很大,則內插補點查找算法優于二分查找算法。

代碼實作

public class Test {
	/*
	 * 插值查找
	 */
	public static int interpolationSearch(int[] arr, int target , int left , int right) {
		if(target < arr[left] || target > arr[right] || left > right){
			return -1;				
		}

		int leftToTarget = target - arr[left];
		int rightToLeft = arr[right] - arr[left];
		int num = right - left;
		//中間的值的下标 注意此行與二分查找的差別
		int mid = left + leftToTarget*num/rightToLeft;
		System.out.println("找了一次,中間值為:" + mid);
		//目标值小于中間值  取左側
		if(arr[mid] > target && left <= right) {
			//遞歸左側
			return interpolationSearch(arr,target , left , mid-1);
		}else if(arr[mid] < target && left <= right) {
			//目标值大于中間值  取右側 并且遞歸
			return interpolationSearch(arr,target, mid+1 , right);
		}else {
			//不大于也不小于 則是相等
			return mid;
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("======== 內插補點查找分布略微不均勻數組 ===========");
		int arr[] = {1 ,4, 9, 15, 27, 49, 128, 157};
		int result = interpolationSearch(arr, 9, 0, arr.length - 1);
		System.out.println(result==-1?"未找到元素!":"查找成功!" + 9 + "對應下标: " + result);
		
		System.out.println("======== 內插補點查找分布均勻數組 ===========");
		int arr1[] = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18 ,20};
		int target = 8;
		int result1 = interpolationSearch(arr1, target, 0, arr1.length - 1);
		System.out.println(result1==-1?"未找到元素!":"查找成功!" + target + "對應下标: " + result1);
		
		System.out.println("======== 內插補點查找分布極其不均勻數組 ===========");
		int arr2[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,99999};
		int target2 = 8;
		int result2 = interpolationSearch(arr2, target2, 0, arr2.length - 1);
		System.out.println(result2==-1?"未找到元素!":"查找成功!" + target2 + "對應下标: " + result2);
		
		
	}

}
           

測試結果如下

Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作
Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作
Java插值查找算法詳解引言什麼是內插補點查找算法代碼實作