# 插值搜尋法(Interpolation Search)
插值搜尋法(Interpolation search)是假設資料呈線性相關分布,利用斜率公式來計算猜測搜尋鍵值的位置。搜尋方式與二分搜尋相同。
回想一下剛剛二元搜尋法,假設我們今天有一陣列如下,並且要搜尋 `1` :
```java=
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<Integer> resIndexList = binarySearch(arr, 0, arr.length - 1, 1);
```
使用二元搜尋法我們搜尋 `1` 需要調用 4 次,實際上這樣並不是很有效率,因為我們需要找到中間值再去找到 `1`。那麼,我們需要一種能夠快速定位到我們想要搜尋的值的方法,插值搜尋法可以實現我們想要達到的效果。
## 原理說明
1. 插值搜尋法其實和二元搜尋很類似,不一樣之處在於插值是每次從自訂的 mid 處開始搜尋。
2. 將折半搜尋中的求 mid 索引的公式,low 表示左邊索引,high表示右邊索引。
3. `int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);`
以下圖為例說明此公式是怎麼來的:
- left = x1 , right = x2
- arr[left] = arr[x1] , arr[right] = arr[x2]
- findVal = arr[m]

我們使用第三點的公式再回頭來算,我們需要幾次才能在這個陣列中搜尋到 `1` 呢?
```java=
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<Integer> resIndexList = insertSearch(arr, 0, arr.length - 1, 1);
```
計算 mid:`int mid = 0 + (9 - 0) * (1 - 1) / (10 - 0) = 0`
直接就找到 1 在`arr[0]`。
假設要搜尋 `8`
計算 mid:`int mid = 0 + (9 - 0) * (8 - 1) / (10 - 0) = 6.3`
直接就找到 8 在`arr[7]`。 ***小數無條件進位**
也因此,插值很適合用在**數據量龐大**並且**陣列比較連續**的情況。
## 程式碼實現
基本上和二元搜尋法很像,不同之處在於二元搜尋法是一次砍半去找,而插值搜尋法是直接使用公式定位搜尋,程式碼的部分也僅有 mid 值不同而已,所以優化部分(陣列中含有多個相同值)直接參考二元搜尋法的即可。
```java=
package Search;
import java.util.Arrays;
public class InsertSearch {
public static void main(String[] args) {
int arr[] = new int[100];
for(int i = 0; i < 100 ; i++) {
arr[i] = i + 1;
}
System.out.println("原陣列: "+Arrays.toString(arr));
int index = insertValueSearch(arr, 0, arr.length - 1, 55);
System.out.println("55 在陣列中索引為: "+index);
}
/**
* 插值搜尋要求陣列有序
* @param arr 傳入的陣列
* @param left 左邊索引
* @param right 右邊索引
* @param findVal 搜尋的值
* @return 找到返回對應的索引, 否則返回 -1
*/
public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
if(left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) { // 沒找到 或 搜尋的值大於小於陣列數值範圍
return -1;
}
int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
int midVal = arr[mid];
if(findVal > midVal) { // 向右遞迴
return insertValueSearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) { // 向左遞迴
return insertValueSearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
}
```
output
```java=
原陣列: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
55 在陣列中索引為: 54
```
## 注意事項
1. 對於數據量較大,關鍵字分佈比較均勻的陣列來說,採用插值斯巡速度較快。
2. 關鍵字分佈不均勻的情況下,該方法不一定比二元搜尋法要好。