斐波那契(黃金分割法)查詢演算法
技術標籤:演算法與資料結構斐波那契數列斐波那契查詢演算法黃金分割法查詢演算法演算法
文章目錄
一、斐波那契查詢演算法概述
1.1 什麼是斐波那契查詢演算法
斐波那契搜尋(Fibonacci search) ,又稱斐波那契查詢,是區間中單峰函式的搜尋技術。斐波那契查詢就是在二分查詢的基礎上根據斐波那契數列進行分割的。
斐波那契查詢同樣是查詢演算法家族中的一員,它要求資料是有序的(升序或降序)。斐波那契查詢採用和二分查詢/插值查詢相似的區間分割策略,都是通過不斷的分割區間縮小搜尋的範圍
1.2 什麼是斐波那契數列
要想具體學習斐波那契查詢演算法,就不得不先了解一下斐波那契數列。
斐波那契數列(Fibonacci sequence),又稱黃金分割數列,因數學家萊昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖為例子而引入,故又稱為 “兔子數列”。
如下圖所示,就是一個斐波那契數列:
在數學上,斐波那契數列被以如下遞推的方法定義:
F
(
0
)
=
0
,
F
(
1
)
=
1
F(0) = 0, \ F(1) = 1
F(0)=0,F(1)=1
F
(
n
)
=
F
(
n
−
1
)
+
F
(
n
−
2
)
(
n
≥
2
,
n
∈
N
∗
)
F(n) = F(n-1) + F(n-2) \qquad (n≥2,n∈N^*)
從斐波那契的遞推公式我們可以總結出重要一點:斐波那契數列從第三項開始,每一項都等於前兩項之和。
1.3 斐波那契查詢演算法的思想
由 1.2 我們可以得知斐波那契數列的特點,而我們可以利用它的特點來做區間分割:可以將一個長度為 F(n) 的陣列看作左右兩段,左邊一段長度是 F(n-1),右邊一段長度是 F(n-2)。
正是上面這樣的區間分割想法,使斐波那契數列和陣列聯絡到了一起。這種分割思想亦是斐波那契查詢演算法的基礎。
斐波那契查詢演算法相對於二分查詢和插值查詢的基本思路是一致的,其中最重要的區別就是它們的查詢點(或稱中間值)的確定。斐波那契查詢演算法的查詢點的計算公式如下
m i d = l e f t + F ( n − 1 ) − 1 mid = left+F(n-1)-1 mid=left+F(n−1)−1
【基本思想】
完整的斐波那契查詢的基本思想如下:在斐波那契數列找一個等於或第一個大於查詢表中元素個數的數 F[n],然後將原查詢表的長度擴充套件為 Fn (如果要補充元素,則重複補充原查詢表最後一個元素,直到原查詢表中滿足 F[n] 個元素);擴充套件完成後進行斐波那契分割,即 F[n] 個元素分割為前半部分 F[n-1] 個元素,後半部分 F[n-2] 個元素;接著找出要查詢的元素在哪一部分並遞迴,直到找到。
二、斐波那契查詢的基本步驟
從上面我們知道了斐波那契查詢演算法的基本思想,根據它的基本思想,斐波那契查詢的基本步驟可以分為以下幾步:
- 構建斐波那契數列;
- 找出查詢表長度對應的斐波那契數列中的元素 F(n);
- 如果查詢表長度小於斐波那契數列中對應的元素 F(n) 的值,則補充查詢表(以查詢表最後一個元素補充);
- 根據斐波那契數列特點對查詢表進行區間分隔,確定查詢點
mid = left+F(n-1)-1
(減 1 是因為陣列下標從 0 開始); - 判斷中間值
arr[mid]
和目標值的關係,確定下一步策略:- 如果目標值小於中間值,說明目標值在左區間。由於左區間長度為 F(n-1),因此 n 應該更新為 n-1,然後再次執行 4、5 兩步;
- 如果目標值大於中間值,說明目標值在右區間。由於右區間長度為 F(n-2),因此 n 應該更新為 n-2,然後再次執行 4、5 兩步;
- 如果目標值等於中間值,說明找到了目標值。但此時還需判別該目標值是原查詢表中的元素還是填充元素:
- 如果是原查詢表中的元素,直接返回索引;
- 如果是填充元素,則返回原查詢表的最後一個元素的索引,即
arr.length-1
。(因為擴充套件陣列是以原查詢表最後一個元素來填充,如果目標值是填充元素,則說明原查詢表最後一個元素值就是目標值)
三、斐波那契查詢的程式碼實現
根據斐波那契查詢演算法的基本步驟,實現出的程式碼如下:
/**
* 斐波那契查詢演算法
* @param arr 查詢表
* @param findValue 目標值
* @return int 目標值在查詢表中的索引
*/
public static int fibonacciSearch(int[] arr, int findValue){
int i = 0;
int mid; // 中間值
int left = 0; // 區間左端
int right = arr.length-1; // 區間右端
// 1. 建立斐波那契數列
int[] fibonacci = getFibonacci(20);
// 2. 獲取斐波那契數列中等於或者第一個大於陣列長度的數
while(fibonacci[i] < arr.length){
i++;
}
// 3. 按照斐波那契數列中的元素長度拷貝一個查詢表
int[] temp = Arrays.copyOf(arr, fibonacci[i]);
// 4. 以原查詢表最後一個元素補齊臨時查詢表長度
for (int j=arr.length; j<temp.length; j++){
temp[j] = arr[arr.length-1];
}
// 5. 迴圈判斷
while (left <= right){
mid = left + fibonacci[i-1]-1; // 計算查詢點
if (temp[mid] < findValue){ // 如果查詢點小於目標值,說明在右區間
left = mid + 1; // 右區間起點
i -= 2; // 右區間長度是 f[i-2],所以要把 i 換成 i-2
}else if (temp[mid] > findValue){ // 如果查詢點大於目標值,說明在左區間
right = mid - 1; // 左區間終點
i -= 1; // 左區間長度是 f[i-1],根據所以要把 i 換成 i-1
}else{ // 如果相等,說明找到了
/* 找到存在兩種可能:一是找到的是原查詢表中的元素,二是找到的是填充值。因此需要判別*/
if (mid <= right){ // 如果是原查詢表中的元素,直接返回索引
return mid;
}else{ // 如果找到的是填充值,則返回原查詢表最後一個索引
return right;
}
}
}
return -1;
}
/**
* 建立斐波那契數列
* @param maxSize 斐波那契數列長度
* @return int[] 斐波那契數列
*/
public static int[] getFibonacci(int maxSize){
int[] fibonacci = new int[maxSize];
fibonacci[0] = 0;
fibonacci[1] = 1;
for (int i=2; i<maxSize; i++){
fibonacci[i] = fibonacci[i-1] + fibonacci[i-2];
}
return fibonacci;
}