斐波那契查找-类似于二分查找

来源:互联网 发布:淘宝女包店铺 编辑:程序博客网 时间:2024/04/28 18:43
  1. /*斐波那契查找法,前提是线性表必须有序,时间复杂度是O(logn)*/  
  2. #include<iostream>  
  3.   
  4. const int MAXSIZE = 20;  
  5.   
  6. int Fibonacci_Search(int *a, int n, int key);  
  7.   
  8. /*用非递归法构造一个斐波那契数组*/  
  9. void Fibonacci(int *f)  
  10. {  
  11.     f[0] = 1;  
  12.     f[1] = 1;  
  13.       
  14.     for(int i=2; i<MAXSIZE; i++)  
  15.     {  
  16.         f[i] = f[i-1] + f[i-2];  
  17.     }  
  18. }  
  19.   
  20. int main()  
  21. {     
  22.     int array[] = {0,16,24,35,47,59,62,73,88,99};  
  23.       
  24.     int number = Fibonacci_Search(array, sizeof(array)/sizeof(int), 73);  
  25.     std::cout<<"位置是:array["<<number<<"]\n";  
  26.     return 0;  
  27. }  
  28.   
  29. /*定义斐波那契查找法*/  
  30. int Fibonacci_Search(int *a, int n, int key)  
  31. {  
  32.     int low, high, mid, i, k;  
  33.     int F[MAXSIZE];  
  34.   
  35.     Fibonacci(F); //构造一个斐波那契数组F  
  36.     low = 1;   //最低下标记录为首位  
  37.     high = n;  //最高下标记录为末位  
  38.     k = 0;  
  39.   
  40.     while(n > F[k]-1)  //计算n位于斐波那契数列的位置  
  41.     {  
  42.         k++;  
  43.     }  
  44.   
  45.     for(i=n; i<F[k]-1; i++)  //将a的元素扩展到(某斐波那契数 - 1),即F[k]-1  
  46.     {  
  47.         a[i] = a[n];  
  48.     }  
  49.   
  50.     while(low <= high)  
  51.     {  
  52.         mid = low + F[k-1] - 1;   //计算当前分割的下标  
  53.         if(key < a[mid])  
  54.         {  
  55.             high = mid - 1;  
  56.             k -= 1;  
  57.         }  
  58.         else if(key > a[mid])  
  59.         {  
  60.             low = mid + 1;  
  61.             k -= 2;  
  62.         }  
  63.         else  
  64.         {  
  65.             if(mid <= n)  
  66.                 return mid;   //若相当则说明mid即为查找到的位置  
  67.             else  
  68.                 return n;     //若mid>n则说明是扩展的数值,返回n  
  69.         }  
  70.     }  
  71.     return -1;  
  72. }  

       首先要明确:如果一个有序表的元素个数为n,并且n正好是(某个斐波那契数 - 1),即n=F[k]-1时,才能用斐波那契查找法。 如果有序表的元素个n不等于(某个斐波那契数 - 1),即n≠F[k]-1,这时必须要将有序表的元素扩展到大于n的那个斐波那契数 - 1才行,这段代码:

[cpp]view plaincopyprint?
  1. for (int i = n; i < F[k] - 1; i++)  
  2. {  
  3.     a[i] = a[n];  
  4. }  


便是这个作用。

      数组a的长度其实很好估算,比如你定义了有10个元素的有序数组a[20],n=10,那么n就位于8和13,即F[6]和F[7]之间,所以k=7,此时数组a的元素个数要被扩充,为:F[7] - 1 = 12个; 再如你定义了一个b[20],且b有12个元素,即n=12,那么很好办了,n = F[7]-1 = 12, 用不着扩充了; 又或者n=8或9或11,则它一定会被扩充到12; 再如n=13,最后得出n位于13和21,即F[7]和F[8]之间,此时k=8,那么F[8]-1 = 20,数组a就要有20个元素了。 所以,n = x(13<=x<=20)时,最后都要被扩充到20;类推,如果n=25呢,则数组a的元素个数肯定要被扩充到 34 - 1 = 33个(25位于21和34,即F[8]和F[9]之间,此时k=9,F[9]-1 = 33),所以,n = x(21<=x<=33)时,最后都要被扩充到33。也就是说,最后数组的元素个数一定是(某个斐波那契数 - 1),这就是一开始说的n与F[k]-1的关系。

对于二分查找,分割是从mid=(low+high)/2开始;而对于斐波那契查找,分割是从mid = low + F[k-1] - 1开始的; 通过上面知道了,数组a现在的元素个数为F[k]-1个,即数组长为F[k]-1,mid把数组分成了左右两部分, 左边的长度为:F[k-1] - 1, 那么右边的长度就为(数组长-左边的长度-1), 即:(F[k]-1) - (F[k-1] - 1) = F[k] - F[k-1] - 1 = F[k-2] - 1。
斐波那契查找的核心是:
1)当key=a[mid]时,查找成功;
2)当key<a[mid]时,新的查找范围是第low个到第mid-1个,此时范围个数为F[k-1] - 1个,即数组左边的长度,所以要在[low, F[k - 1] - 1]范围内查找;
3)当key>a[mid]时,新的查找范围是第mid+1个到第high个,此时范围个数为F[k-2] - 1个,即数组右边的长度,所以要在[F[k - 2] - 1]范围内查找。

 

   关于斐波那契查找, 如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去,对处于当众的大部分数据,其工作效率要高一些。所以尽管斐波那契查找的时间复杂度也为O(logn),但就平均性能来说,斐波那契查找要优于折半查找。可惜如果是最坏的情况,比如这里key=1,那么始终都处于左侧在查找,则查找效率低于折半查找。

   还有关键一点,折半查找是进行加法与除法运算的(mid=(low+high)/2),插值查找则进行更复杂的四则运算(mid = low + (high - low) * ((key - a[low]) / (a[high] - a[low]))),而斐波那契查找只进行最简单的加减法运算(mid = low + F[k-1] - 1),在海量数据的查找过程中,这种细微的差别可能会影响最终的效率。

0 0