斐波那契查找原理深入

斐波那契查找原理:

       斐波那契查找是一种在有序表中高效查找指定元素的算法,比折半查找要复杂一些,主要复杂在要多做不少准备工作。下面看它的工作流程:

        1.计算并保存一个斐波那契序列的数组,方便以后取值。数组名记为f,例如f[1]=1,f[2]=1,f[3]=2,f[4]=3,f[5]=5,f[6]=8,f[7]=13,f[8]=21

        2.把有序数组的长度扩充到a.length=f[k]-1,k是满足条件的最小值,比如数组长度为13,那么就把它长度扩充到f[8]-1=20,所有在末尾添加的扩充元素都是原数组最后一个元素的复制品

        3.找到mid元素,不断进行二分比较,直到找到目标元素为止,这一步的做法与折半查找一模一样,仅仅是计算mid的公式从(low+high)/2改为low+(f[k-1]-1)。

        斐波那契查找的理解难点就一个:为什么需要把数组长度扩充到f[k]-1而不是f[k]或者f[k+1]?这是为了能正确递归计算mid值,看下图可发现 f[k]-1 = (f[k-1] + f[k-2]) - 1 = (f[k-1]-1) + 1 + (f[k-2]-1),中间的1就是我们二分的锚点mid,如果目标在左区,数组长度就缩到(f[k-1]-1),如果在右区,数组长度就缩到(f[k-2]-1),否则就等于mid完成查找。而(f[k-1]-1)又能拆成(f[k-2]-1)+1+(f[k-3]-1),这样递归分割下去就能不断的缩小区间直至找到目标。

                                                   

斐波那契查找与折半查找的比较:

           二者理论效率半斤八两,时间复杂度都是log2n,有人说斐波那契查找比折半查找效率高,理由有2个:1.斐波那契查找只用到加减法,而折半查找计算mid时要除以2,除法很影响效率;2.如果目标在low->mid区,只需要判断一次,而如果在mid->high需要判断2次(需要先判断不在low->mid区,再判断在mid->high区),斐波那契查找的low->mid区更大(0.618>0.5),有更多的概率只需要判断一次,所以总体判断次数更少。

        原因1看起来有理,可是现在的编译器只要遇到/2操作,都会优化为>>1,位运算比加减法只快不慢,所以原因1不成立。原因2也貌似有些道理,可是如果按照这个道理推理下去,把分割点设在0.99岂不是更好?可0.99明显是个垃圾分割点,二分力度很差,所以这个理由我也不认可。

       斐波那契查找的时间复杂度还是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个元素的后半段,继续查找,如此反复,直到查找成功或失败,这样就把斐波那契数列应用到查找算法中了。

 比如上面的表格中的数据元素为11,在斐波那契数列中是找不到,所以在末尾填充元素,例如在上述的位序中的11,12处添加21,21(重复的是末尾元素。)

代码实现:

/*

@para:a[]:给定待查的数组,n为要查找的数组的个数,key:为要查找的关键字

*/
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)//计算n位于斐波那契数列中的位置
		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;
            /*说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的 
              元素个数为F(k-1)-1个,所以可以递归的应用斐波那契查找*/
		}
		else if (key>a[mid])
		{
			low=mid+1;		
			k=k-2;//说明:low=mid+1说明待查找的元素在[mid+1,hign]范围内,k-=2 说明范围 
                  //[mid+1,high]内的元素个数为n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k- 
                  //2)-1个,所以可以递归的应用斐波那契查找

		}
		else //前面两种情况都不满足,才会执行else
		{
			if (mid<=n)
				return mid;		/* 若相等则说明mid即为查找到的位置 */
			else 
				return n;
		}
		
	}
	return 0;
}

大部分说明都忽略了一个条件的说明:n=F(k)-1, 表中记录的个数为某个斐波那契数小1。这是为什么呢?

我想了很久,终于发现,原因其实很简单:

是为了格式上的统一,以方便递归或者循环程序的编写。表中的数据是F(k)-1个,使用mid值进行分割又用掉一个,那么剩下F(k)-2个。正好分给两个子序列,每个子序列的个数分别是F(k-1)-1与F(k-2)-1个,格式上与之前是统一的。不然的话,每个子序列的元素个数有可能是F(k-1),F(k-1)-1,F(k-2),F(k-2)-1个,写程序会非常麻烦

  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值