詳細描述
二分查詢是通過折半的方法,每一次都將搜尋範圍縮小至原來的二分之一,如果這個折半能夠實現到折四分之一甚至更多,效率將會更高。
插值查詢就是這樣的演算法,類似於二分查詢,插值查詢每次會從自適應處開始查詢,實質上是將 \(\frac1 2\) 處位置的查詢公式做了修改:
\[mid = \frac{low + high}{2} = low + \frac{1}{2}(high - low) \Rightarrow mid = low + \frac{key - a[low]}{a[high] - a[low]}(high - low)
\]
插值查詢詳細的執行步驟如下:
- 在有序表中,通過比例公式取對應記錄作為比較物件;
- 若給定值與對應記錄的關鍵字相等,則查詢成功;
- 若給定值小於對應記錄的關鍵字,則在對應記錄的左半區繼續查詢;
- 若給定值大於對應記錄的關鍵字,則在中間記錄的右半區繼續查詢;
- 不斷重複上述過程,直到查詢成功,或所有查詢區域無記錄,查詢失敗為止。
問題解疑
插值查詢為什麼是 \(\frac{key - a[low]}{a[high] - a[low]}\)?
打個比方,在一本英文字典中查詢 apple 這個單詞的時候,肯定不會從字典中間開始查詢,而是從字典開頭部分開始翻,因為會覺得這樣的找法才是比較快的。
對於一個有序的序列,如果能在查詢前較準確的預測關鍵字在序列中的位置時,這樣的查詢方法能比二分查詢擁有更好的效能。
其中的差值公式 \(\frac{key - a[low]}{a[high] - a[low]}\) 是要將查詢的關鍵字與序列中的最大、最小記錄的關鍵字比較,獲取一個相對更準確的位置。
使用插值查詢有哪些注意事項?
對於均勻分佈的序列,插值查詢的效率是非常快。特別是對於絕對均勻分佈的序列(相鄰元素差值相同),插值查詢可以只做一次比較就查詢成功。
對於分佈很不均勻的序列,插值查詢的計算則會起到反效果,這時候反而不如二分查詢。
程式碼實現
查詢介面
package cn.fatedeity.algorithm.search;
public interface Search {
int search(int[] numbers, int target);
}
插值查詢類
package cn.fatedeity.algorithm.search;
/**
* 插值查詢類
*/
public class InterpolationSearch implements Search {
private int search(int[] numbers, int target, int left, int right) {
if (left > right) {
return -1;
} else if (left == right) {
if (numbers[left] == target) {
return left;
} else {
return -1;
}
}
if (target < numbers[left] || target > numbers[right]) {
return -1;
}
int scale = (target - numbers[left]) / (numbers[right] - numbers[left]);
int mid = left + (int) Math.floor(scale * (right - left));
if (numbers[mid] > target) {
return this.search(numbers, target, left, mid - 1);
} else if (numbers[mid] < target) {
return this.search(numbers, target, mid + 1, right);
} else {
return mid;
}
}
@Override
public int search(int[] numbers, int target) {
return this.search(numbers, target, 0, numbers.length - 1);
}
}