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

斐波那契

一、摘要

如果從文件中讀取的數據記錄的關鍵字是有序排列的(遞增的或是遞減的),則可以用一種比折半查找法更有效率的查找方法來查找文件中的記錄,即爲斐波那契查找,也稱爲黃金分割查找法。

二、什麼是斐波那契查找

斐波那契數列,又稱黃金分割數列,指的是這樣一個數列: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],將原查找表擴展爲長度爲Fn,完成後進行斐波那契分割,即F[n]個元素分割爲前半部分F[n-1]個元素,後半部分F[n-2]個元素,找出要查找的元素在那一部分並遞歸,直到找到。
斐波那契查找的時間複雜度還是O(log 2 n ),但是 與折半查找相比,斐波那契查找的優點是它只涉及加法和減法運算,而不用除法,而除法比加減法要佔用更多的時間,因此,斐波那契查找的運行時間理論上比折半查找小,但是還是得視具體情況而定。

四、舉例

對於斐波那契數列:1、1、2、3、5、8、13、21、34、55、89……(也可以從0開始),前後兩個數字的比值隨着數列的增加,越來越接近黃金比值0.618。比如這裏的89,把它想象成整個有序表的元素個數,而89是由前面的兩個斐波那契數34和55相加之後的和,也就是說把元素個數爲89的有序表分成由前55個數據元素組成的前半段和由後34個數據元素組成的後半段,那麼前半段元素個數和整個有序表長度的比值就接近黃金比值0.618,假如要查找的元素在前半段,那麼繼續按照斐波那契數列來看,55 = 34 + 21,所以繼續把前半段分成前34個數據元素的前半段和後21個元素的後半段,繼續查找,如此反覆,直到查找成功或失敗,這樣就把斐波那契數列應用到查找算法中了。

在這裏插入圖片描述

從圖中可以看出,當有序表的元素個數不是斐波那契數列中的某個數字時,需要把有序表的元素個數長度補齊,讓它成爲斐波那契數列中的一個數值,當然把原有序表截斷肯定是不可能的,不然還怎麼查找。然後圖中標識每次取斐波那契數列中的某個值時(F[k]),都會進行-1操作,這是因爲有序表數組位序從0開始的,純粹是爲了迎合位序從0開始。

五、如何計算

作爲折半查找法的加強優化版,它很好地將黃金分割的思想融入到關鍵字的查找中,它是根據斐波那契序列的特點對有序表進行分割的。

1、斐波那契數組

斐波那契查找,是二分查找的一個變種。其時間複雜度 O(log2n) 。
斐波那契數列,又稱黃金分割數列, F{1,1,2,3,5,8,13,21,34,…}F={1,1,2,3,5,8,13,21,34,…} 數學表達式:
在這裏插入圖片描述
之所以它又稱爲黃金分割數列,是因爲它的前一項與後一項的比值隨着數字數量的增多逐漸逼近黃金分割比值0.618。

2、利用斐波那契數組確定元素的位置

所以斐波那契查找改變了二分查找中原有的中值 mid 的求解方式,其 mid 不再代表中值,而是代表了黃金分割點:
在這裏插入圖片描述
二分查找,以一半元素來分割數組;
斐波那契查找,以黃金分割點來分割數組。
假設表中有 n 個元素,查找過程爲取區間中間元素的下標 mid ,對 mid 的關鍵字與給定值的關鍵字比較:
(1)如果與給定關鍵字相同,則查找成功,返回在表中的位置;
(2)如果給定關鍵字大,向右查找並減小2個斐波那契區間;
(3)如果給定關鍵字小,向左查找並減小1個斐波那契區間;
(4)重複過程,直到找到關鍵字(成功)或區間爲空集(失敗)。

它要求開始表中記錄的個數爲某個斐波那契數小1,及n=F(k)-1;
開始將k值與第F(k-1)位置的記錄進行比較(即mid=low+F(k-1)-1),比較結果也分爲三種
(1)key = k[mid], mid位置的元素即爲所求;
(2)key>k[mid],low=mid+1,k-=2;
說明:low=mid+1說明待查找的元素在[mid+1,high]範圍內,k-=2 說明範圍[mid+1,high]內的元素個數爲n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1個,所以可以遞歸的應用斐波那契查找。
(3)key<k[mid],high=mid-1,k-=1。
說明:low=mid+1說明待查找的元素在[low,mid-1]範圍內,k-=1 說明範圍[low,mid-1]內的元素個數爲F(k-1)-1個,所以可以遞歸 的應用斐波那契查找。

斐波那契搜索也是二分查找的一種提升算法,通過運用黃金比例的概念在數列中選擇查找點進行查找,提高查找效率。同樣的,斐波那契查找也屬於一種有序查找算法。

3、舉例

舉例,假設有集合 e0=2, e1=3, e2=7, e3=14, e4=22, e5=33, e6=55, e7=75, e8=89, e9=123 。
查找元素44的過程:
已知:
元素數量size = 10;
下標區間 [low, high] 爲left = 0、right = 9。
則初始化的斐波那契數列爲 Fib={1,1,2,3,5,8,13}Fib={1,1,2,3,5,8,13} ,即斐波那契數列最後一位要比size - 1大。
此時查找第一步爲:此時剛初始化完,斐波那契數列最後一個區間爲第6個區間,即區間(8, 13);第8個數字爲當前黃金分割點,則第8個數字下標爲下標7,檢測的數字爲 e7 ;以8分割數組後,左區間爲(1, 8)。
在這裏插入圖片描述
此時查找第二步爲:同第一步,黃金分割點爲第5個數字下標爲4。
在這裏插入圖片描述
上一步結束,還剩 { 33 55 } ,此時查找第三步爲:黃金分割點爲第2個數字55,它的下標爲6。
在這裏插入圖片描述
上一步結束,還剩 { 33 } ,此時查找第四步爲:黃金分割點爲第1個數字33,它的下標爲5。

在這裏插入圖片描述

六、代碼實現

1、斐波那契數組實現

//生成斐波那契數列
void  Fibonacci(int* F)
{
	int i = 0;
	F[0] = 1;
	F[1] = 1;
	for ( i = 2; i < N; ++i)
	{
		F[i] = F[i - 1] + F[i - 2];
		
	}
}

2、斐波那契查找

int search(int* a, int  key, int n)
{
	int i = 0, low = 0, high = n - 1;
	int mid = 0, k = 0, F[N];
	Fibonacci(F);
	while (n >F[k] - 1)  //計算出N在斐波那契中的數列項
	{
		++k;
	}
	//當數組不滿足時補全數組
	for ( i = 0; i < F[k]-1; ++i)
	{
		a[i] = a[high];   //用數組最後一項補全數組
	}
	while (low <= high)
	{
		mid = low + F[k - 1] - 1; //根據斐波那契數列進行分割
		if (a[mid] > key)   //更小時,在左邊
		{
			high = mid - 1;
			k -= 1;   //左邊:F[N-1] - 1  總式: F[N] - 1 = F[N-1] - 1 + F[N-2] -1 + 1
		}
		else if(a[mid] < key)
		{
			low = mid + 1; 
			k -= 2;  // 右邊:F[N - 12] - 1 
		}
		else
		{
			if (mid <= high)//如果爲真則找到相應位置 
				return mid;
			else
				return -1;
		}

	}
}

3、實驗數據及測試

#include<stdio.h>
#define N 30


//生成斐波那契數列
void  Fibonacci(int* F)
{
	int i = 0;
	F[0] = 1;
	F[1] = 1;
	for ( i = 2; i < N; ++i)
	{
		F[i] = F[i - 1] + F[i - 2];

	}
}
int search(int* a, int  key, int n)
{
	int i = 0, low = 0, high = n - 1;
	int mid = 0, k = 0, F[N];
	Fibonacci(F);
	while (n >F[k] - 1)  //計算出N在斐波那契中的數列項
	{
		++k;
	}
	//當數組不滿足時補全數組
	for ( i = 0; i < F[k]-1; ++i)
	{
		a[i] = a[high];   //用數組最後一項補全數組
	}
	while (low <= high)
	{
		mid = low + F[k - 1] - 1; //根據斐波那契數列進行分割
		if (a[mid] > key)   //更小時,在左邊
		{
			high = mid - 1;
			k -= 1;   //左邊:F[N-1] - 1  總式: F[N] - 1 = F[N-1] - 1 + F[N-2] -1 + 1
		}
		else if(a[mid] < key)
		{
			low = mid + 1; 
			k -= 2;  // 右邊:F[N - 12] - 1 
		}
		else
		{
			if (mid <= high)//如果爲真則找到相應位置 
				return mid;
			else
				return -1;
		}

	}
}
int main()
{
	int a[N] = { 2,3,5,7,9,11,12,15,19,22 };
	int k, r = 0;
	printf("請輸入要查找的數字:");
	scanf_s("%d", &k);
	r = search(a, k, 10);
	if (r != -1)
		printf("\n在數組的第%d個位置找到元素:%d\n", r + 1, k);
	else
		printf("\n未在數組中找到該元素:%d\n", k);
	return 0;
}

在這裏插入圖片描述

4、編譯環境

Visual Studio 2019

七、後記

折半查找法https://blog.csdn.net/qq_45768871/article/details/104975755
插值查找(比例查找法)https://blog.csdn.net/qq_45768871/article/details/104975698

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章