相比于折半查找,插值查找,斐波那契查找的实现相对复杂,下面详细介绍一下该算法:
既然叫斐波那契查找,首先得弄明白什么是斐波那契数列。斐波那契数列有一个重要的性质:
前一个数除以相邻的后一个数,比值无限接近黄金分割
原理详解
斐波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数减1,即n=F[k]-1;
算法核心:
精髓:采用最接近查找长度的斐波那契数值来确定拆分点。
举个例子来讲,现有长度为9的数组,要对它进行拆分,对应的斐波那契数列(长度先随便取,只要最大数大于9即可){1,1,2,3,5,8,13,21,34},不难发现,大于9且最接近9的斐波那契数值是f[7]=13,为了满足所谓的黄金分割,所以它的第一个拆分点应该就是f[7]的前一个值f[6]=8,即待查找数组array的第8个数,对应到下标就是array[8],依次类推。
推演到一般情况,假设有待查找数组array[n]和斐波那契数组F[k],并且n满足n>=F[k]-1&&n < F[k+1]-1,则它的第一个拆分点middle=F[k-1],即mid = low+F[k-1]-1。所以我们可以得到:当n=F[k]-1时,mid = low+F[k-1]-1。当n=F[k-2]-1时,mid = low+F[k-3]-1,令k=k-2,则mid = low+F[k-1]-1。
注意:如果n刚好等于F[k]-1,即待查找数组刚好拆成F[k-1]和F[k-2]两部分,此时正好拆分成两半;然而大多数情况不尽人意,n会小于F[k]-1,这时候要拆成完整F[k-1]和残疾的F[k-2]两部分,需要将不满的数值补齐。
for (i = n; i<F[k] - 1; i++) /*将不满的数值补齐*/
a[i] = a[n];
比较结果也分为三种:
1)相等,mid位置的元素即为所求
2)> ,low=mid+1,k=k-2;说明:low=mid+1说明待查找的元素在[mid+1,hign]范围内,k=k-2 说明范围[mid+1,high]内的元素个数为n-F[k-1]= F[k]-1-F[k-1]=F[k-1]+F[k-2]-F(k-1)-1=F[k-2]-1个。通过k=k-2得到剩下的元素个数为F[k-2]-1个,因此正好mid = low+F[k-1]-1。
3)< ,high=mid-1,k=k-1;说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k=k-1 说明范围[low,mid-1]内的元素个数为F[k-1]-1个。
大部分说明都忽略了一个条件的说明:n=F(k)-1, 表中记录的个数为某个斐波那契数减1。这是为什么呢?
我想了很久,终于发现,原因其实很简单:
是为了格式上的统一,以方便递归或者循环程序的编写。表中的数据是F(k)-1个,使用mid值进行分割又用掉一个,那么剩下F(k)-2个。正好分给两个子序列,每个子序列的个数分别是F(k-1)-1与F(k-2)-1个,格式上与之前是统一的。
条件:
1)数据必须采用顺序存储结构;
2)数据必须有序
原理:
1)最接近查找长度的斐波那契值来确定拆分点
2)黄金分割
时间复杂度:
与拆半查找一样,也是logn,注意:斐波那契查找是纯加减运算实现的二分查找。
实现:来自《大话数据结构》
/* 斐波那契查找 */
int Fibonacci_Search(int *a, int n, int key)
{
int low, high, mid, i, k = 0;
low = 1; /* 定义最低下标为记录首位 */
high = n; /* 定义最高下标为记录末位 */
while (n>F[k] - 1)
k++;
for (i = n; i<F[k] - 1; i++) /*将不满的数值补齐*/
a[i] = a[n];
while (low <= high)
{
mid = low + F[k - 1] - 1; /*计算当前分隔的下标,理解这句话非常重要*/
if (key<a[mid]) /*若查找记录小于当前分隔记录*/
{
high = mid - 1; /*最高下标调整到分隔下标*/
k = k - 1; /*斐波那契数列下标减1*/
}
else if (key>a[mid])
{
low = mid + 1;
k = k - 2; /*下标减2*/
}
else
{
if (mid <= n)
return mid; /* 若相等则说明mid即为查找到的位置 */
else
return n; /*若mid>n说明是补全数值,返回n*/
}
}
return 0;
}