1. 程式人生 > 其它 >斐波那契查詢(黃金分割法查詢)演算法

斐波那契查詢(黃金分割法查詢)演算法

技術標籤:Java資料結構Java資料結構斐波拉契查詢java

一、斐波那契查詢演算法基本思想

斐波那契查詢演算法又稱為黃金分割法查詢演算法,是一種有序查詢演算法

斐波那契數列,又稱黃金分割數列,指的是這樣一個數列:1、1、2、3、5、8、13、21、····。在數學上,斐波那契被遞迴方法如下定義:F(1)=1,F(2)=1,F(n)=f(n-1)+F(n-2) (n>=2)。該數列越往後相鄰的兩個數的比值越趨向於黃金比例值(0.618)。

斐波那契查詢是在二分查詢的基礎上根據斐波那契數列進行分割。在斐波那契數列找一個等於大於查詢表中元素個數的數F(n),將原查詢表擴充套件為長度為F(n)(如果要補充元素,則補充重複最後一個元素,直到滿足F(n)個元素),完成後進行斐波那契分割,即F[n]個元素分割為前半部分F(n-1)個元素,後半部分F(n-2)個元素,找出要查詢的元素在那一部分並遞迴

,直到找到。斐波那契查詢的時間複雜度是O(log2n)

斐波那契查詢原理與二分查詢和插值查詢相似,僅僅改變了中間結點mid的位置,mid不再是通過二分或插值得到,而是位於黃金分割點附近,即mid=low+F(k-1)-1(F代表斐波那契數列),如下圖所示
在這裡插入圖片描述
對F(k-1)-1的理解:

(1)由斐波那契數列 F[k]=F[k-1]+F[k-2] 的性質,可以得到 (F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1 。該式說明:只要順序表的長度為F[k]-1,則可以將該表分成長度為F[k-1]-1和F[k-2]-1的兩段,即如上圖所示。從而中間位置為mid=low+F(k-1)-1;

(2)每一子段也可以用相同的方式分割;
(3)若順序表長度n不一定剛好等於F[k]-1,則需要將原來的順序表長度n增加至F[k]-1。這裡的k值只要能使得F[k]-1恰好大於或等於n即可。

例如:原陣列為{1,5,6,33,55,233,455},長度n為7,若 F[ k ] - 1等於9,則應該將原陣列擴容到9,多出的位置使用high位置的455填充,即得到{1,5,6,33,55,233,455,455,455}。

二、程式碼實現

斐波拉契查詢演算法實現思路:

(1)我們需要先遞迴實現斐波拉契數列,然後根據原陣列的大小計算斐波拉契數列的k值;
(2)陣列擴容條件是:增大 k 值(索引從 0 開始),使得陣列長度剛好大於或者等於斐波那契數列中的 F[k]-1 ,我們定義臨時陣列 temp ,temp 後面為 0 的元素都按照陣列最大元素值填充


(3)mid值的確定:mid = low + f[k - 1] - 1 ,即用黃金分割點確定 mid 的值;
(4)value<temp[mid] :目標值在黃金分割點的左邊,因為全部元素 = 前面的元素 + 後邊元素即 f[k] = f[k-1] + f[k-2],又前面有 f[k-1]個元素,所以可以繼續拆分 f[k-1] = f[k-2] + f[k-3],即下次迴圈有mid=low+f[k-1-1]+1,即需要k-=1
value> temp[mid] :目標值在黃金分割點的右邊。因為全部元素 = 前面的元素 + 後邊元素即 f[k] = f[k-1] + f[k-2],又前面有 f[k-2]個元素,所以可以繼續拆分 f[k-1] = f[k-3] + f[k-4],即下次迴圈有mid=low+f[k-1-2]+1,即需要k-=2
value== temp[mid] :找到目標值,因為陣列經歷過擴容,後面的值其實有些是多餘的,mid 可能會越界(相對於原陣列來說)則有:
1)mid <= high :證明 mid 索引在原陣列中,返回 mid
2)mid > high 時,證明 mid 索引已經越界(相對於原陣列來說),返回 high
(5)若沒有找到則返回-1。

package Search;

import java.util.Arrays;
import java.util.Scanner;

public class FibonacciSearch {
	public static final int maxSize=20;//斐波拉契數列的大小
	
	public static int[] fibonacci() {//構造斐波拉契數列
		int[] fib=new int[maxSize];
		fib[0]=1;
		fib[1]=1;
		for(int i=2;i<maxSize;i++) {
			fib[i]=fib[i-1]+fib[i-2];
		}
		return fib;
	}
	
	public static int fibonaccisearch(int[] arr,int value) {
		int low=0;//指標low表示待查元素所在範圍的下界,下界索引從0開始
		int high=arr.length-1;//指標high表示待查元素所在範圍的上界
		int k=0;//表示斐波那契分割數值的下標
		int mid=0;
		int fib[]=fibonacci();//獲取到斐波那契數列
		
		while(high>fib[k]-1) {//獲取到斐波那契分割數值的下標
			k++;
		}
		
		int[] temp=Arrays.copyOf(arr, fib[k]);//因為f[k]值 可能大於a的 長度,因此我們需要使用Arrays.copyOf方法,構造一個新的陣列,並指向temp[]
		for(int i=high+1;i<temp.length;i++) {//使用arr陣列最後的數填充 temp
			temp[i]=arr[high];
		}
		
		while(low<high) {
			mid=low+fib[k-1]-1;//斐波拉契中間值公式
			if(value<temp[mid]) {//關鍵字值小於中間值,應該向左掃描即使high=mid-1
				high=mid-1;
				k-=1;
			}else if(value>temp[mid]) {//關鍵字值大於中間值,應該向右掃描即使low=mid+1
				low=mid+1;
				k-=2;
			}else {//找到時需要確定返回哪個下標
				if(mid<=high) {//證明mid索引在原陣列中,返回 mid
					return mid;
				}else {//證明mid索引已經越界(相對於原陣列來說),返回 high
					return high;
				}
			}
		}
		return -1;//沒有找到即返回-1
	}

	public static void main(String[] args) {
		int[] arr= {1,5,6,33,55,233,455};
		Arrays.sort(arr);//採用斐波拉契查詢時,資料需是有序不重複的,需要對該陣列進行排序
		Scanner sc=new Scanner(System.in);
		System.out.println("請輸入查詢元素:");
		int value=sc.nextInt();
		System.out.printf("查詢元素 %d 的位置是 %d",value,fibonaccisearch(arr,value));
	}
}

執行結果:

請輸入查詢元素:
55
查詢元素 55 的位置是 4