“斐波那契查找”真的比“二分查找”快么?

语言学习篇:C/C++ 专栏收录该内容
43 篇文章 1 订阅
Is Fibonacci Search really "faster" than Binary Search?

申明:本文讨论的搜索对象为有序数组,不是数学上讨论的函数。
1. 介绍
对经过各种Sort算法排好序之后的有序数组进行检索的Search算法大致有以下三种:线性查找 O(n),二分查找 O(log(n)),斐波那契查找 O(log(n))。

前两者用的比较多,对于 Fibonacci Search,应该蛮多人和我一样只闻其名,不见其人吧。
数学原理如下:
斐波那契数列:0、1、1、2、3、5、8、13、21、……(有人喜欢从1开始,随你~~~)
如果设F(n)为该数列的第n项(n∈N)。那么这句话可以写成如下形式:
F(0) = 0,F(1)=1,F(n)=F(n-1)+F(n-2) (n≥2),显然这是一个线性递推数列,随着数列项数的增加,前一项与后一项之比越来越逼近黄金分割的数值0.6180339887..…
且看通项公式:


算法描述如下:

The Fibonacci Search Algorithm

Let Fk represent the k-th Fibonacci number where Fk+2=Fk+1 + Fk for k>=0 and F0 = 0, F1 = 1. To test whether an item is in a list of n = Fm ordered numbers, proceed as follows:

  1. Set k = m.
  2. If k = 0, finish - no match.
  3. Test item against entry in position Fk-1.
  4. If match, finish.
  5. If item is less than entry Fk-1, discard entries from positions Fk-1 + 1 to n. Set k = k - 1 and go to 2.
  6. If item is greater than entry Fk-1, discard entries from positions 1 to Fk-1. Renumber remaining entries from 1 to Fk-2, set k = k - 2 and go to 2.

If n is not a Fibonacci number, then let Fm be the smallest such number >n, augment the original array with Fm-n numbers larger than the sought item and apply the above algorithm for n'=Fm.

2.实验

 实验数据:产生有序数组
1
2
3
4
5
6
#define NUM  4
int *array = ( int *)calloc(NUM,  sizeof( int));
for ( int i =  0; i < NUM; ++i)
{
    array[i] = i;
}

 实验方法:三种搜索算法函数原型pData-数组,n-数组个数,key-待搜索关键字
1
2
3
4
int LineSearch( int *pData,  int n,  int key);
int BinarySearch( int *pData,  int n,  int key);
int FibonacciSearch( int *pData,  int n,  int key);

方法实现:
 线性查找
1
2
3
4
5
6
7
8
9
10
11
int LineSearch( int *pData,  int n,  int key)
{
     int idx =  0;
     while(idx != n )
    {
         if(pData[idx] != key)
            idx++;
         else
             return idx;
    }
}

 二分查找 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int BinarySearch( int *pData,  int n,  int key)
{
     int center, left =  0, right = n -  1;

     while(left <= right)
    {
        center = (left + right) /  2;
         if (pData[center] > key)
        {
            right = center -  1;
        }
         else
        {
             if (pData[center] < key)
                left = center +  1;
             else
                 return center;
        }
    }
     return - 1;
}

 斐波那契查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/*
  Fibonaccian search for locating the index of "key" in an array "pData" of size "n"
  that is sorted in ascending order. See http://doi.acm.org/10.1145/367487.367496

  Algorithm description
  -----------------------------------------------------------------------------
  Let Fk represent the k-th Fibonacci number where Fk+2=Fk+1 + Fk for k>=0 and
  F0 = 0, F1 = 1. To test whether an item is in a list of n = Fm ordered numbers,
  proceed as follows:
  a) Set k = m.
  b) If k = 0, finish - no match.
  c) Test item against entry in position Fk-1.
  d) If match, finish.
  e) If item is less than entry Fk-1, discard entries from positions Fk-1 + 1 to n.
     Set k = k - 1 and go to b).
  f) If item is greater than entry Fk-1, discard entries from positions 1 to Fk-1.
     Renumber remaining entries from 1 to Fk-2, set k = k - 2 and go to b)

  If Fm>n then the original array is augmented with Fm-n numbers larger
  than key and the above algorithm is applied.
 */

int FibonacciSearch( int *pData,  int n,  int key)
{
     register  int k, idx, offs;
     static  int prevn = - 1, prevk = - 1;

     /*  Precomputed Fibonacci numbers F0 up to F46. This implementation assumes that the size n
     *  of the input array fits in 4 bytes. Note that F46=1836311903 is the largest Fibonacci
     *  number that is less or equal to the 4-byte INT_MAX (=2147483647). The next Fibonacci
     *  number, i.e. F47, is 2971215073 and is larger than INT_MAX, implying that it does not
     *  fit in a 4 byte integer. Note also that the last array element is INT_MAX rather than
     *  F47. This ensures correct operation for n>F46.
     */

     const  static  int Fib[ 47 +  1] = { 011235813213455891442333776109871597258441816765,
                                     1094617711286574636875025121393196418317811514229832040134626921783093524578,
                                     5702887922746514930352241578173908816963245986102334155165580141267914296,
                                     43349443770140873311349031701836311903, INT_MAX
                                   };

     /* find the smallest fibonacci number that is greater or equal to n. Store this
     * number to avoid recomputing it in the case of repetitive searches with identical n.
     */

     if(n != prevn)
    {
         register  int f0, f1, t;
         for(f0 =  0, f1 =  1, k =  1; f1 < n; t = f1, f1 += f0, f0 = t, ++k);

        prevk = k;
        prevn = n;
    }
     else
        k = prevk;

     /* If the sought value is larger than the largest Fibonacci number less than n,
     * care must be taken top ensure that we do not attempt to read beyond the end
     * of the array. If we do need to do this, we pretend that the array is padded
     * with elements larger than the sought value.
     */

     for(offs =  0; k >  0;  )
    {
        idx = offs + Fib[--k];

         /* note that at this point k  has already been decremented once */
         if(idx >= n || key < pData[idx])  // index out of bounds or key in 1st part
        {
             continue;
        }
         else  if (key > pData[idx])
        {
             // key in 2nd part
            offs = idx;
            --k;
        }
         else  // key==pData[idx], found
             return idx;
    }

     return - 1// not found
}

算法正确性验证:
 C++ Code 
1
2
3
printf( "LineSearch Index : %d\n", LineSearch(array, NUM, array[ 3]));
printf( "BinarySearch Index : %d\n", BinarySearch(array, NUM, array[ 3]));
printf( "FibonacciSearch Index : %d\n", FibonacciSearch(array, NUM, array[ 3]));


分析:
NUM=40123total
Line01237
Binary10124
Fibonacci42107
ps:第一行为待查找的关键值,本文将遍历序数组中的每个关键值的查找次数并相加(如果要表征平均,除以NUM即可,因为一样,所以就不除了),来表征平均搜索效率,因为当假设每个值被查找的概率相同,即符合均匀分布,还是有一定的合理性的。即total值越小,对单个关键值的搜索次数约有效率。

算法性能实验:
NUM44E14E24E34E44E54E64E7
Line678079800799800079998000---
Binary41432687399175344816675732
(0.07s)
79805719
(0.81s)
932891163
(20.34s)
Fibonacci71793189424285723247206170
(0.06s)
86747879
(0.77s)
1012299070
(12.31s)
ps:E为以10为底的指数。-表示int已经不能满足要求了,溢出鸟。()中为耗时(平台:gcc+sublime text2)。
得到次数需要改写一些代码,改写的整个代码放在最后附录,需要的自己去验证下。

结论: 与二分查找相比,斐波那契查找的明显优点在于它只涉及加法和减法运算,而不用除法(可能用“>>1”要好点)。因为除法比加减法要占去更多的机时,因此,斐波那契查找的运行时间比二分查找短。  但前者的O(log(n))还是后者的大点,对某一个对象的平均搜索次数要小。所以,要取决于你如何看待“faster”了。

Reference:

附录:
 Search.h 
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <time.h>

#define NUM  4

int LineSearch( int* pData,  int n,  int key);
int BinarySearch( int* pData,  int n,  int key);
int FibonacciSearch( int* pData,  int n,  int key);

  Search.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include  "search.h"

//四种搜索算法 搜索遍历 有序数组所用的总次数

int main( int argc,  char  const *argv[])
{
    clock_t start, finish;
     double time_duration;

     //产生有序数组
     int *array = ( int *)calloc(NUM,  sizeof( int));
     for ( int i =  0; i < NUM; ++i)
    {
        array[i] = i;
    }

    start = clock();
     int tTmp;

     //线性查找
    tTmp =  0;
     for ( int i =  0; i < NUM; ++i)
    {
        tTmp += LineSearch(array, NUM, array[i]);
    }

    printf( "LineSearch Index : %d\n", tTmp);


     //二分查找
    tTmp =  0;
     for ( int i =  0; i < NUM; ++i)
    {
        tTmp += BinarySearch(array, NUM, array[i]);
    }

    printf( "BinarySearch Index : %d\n", tTmp);


     //斐波那契查找
    tTmp =  0;
     for ( int i =  0; i < NUM; ++i)
    {
        tTmp += FibonacciSearch(array, NUM, array[i]);
    }

    printf( "FibonacciSearch Index : %d\n", tTmp);

    free(array);

    finish = clock();
    time_duration = ( double)(finish - start) / CLOCKS_PER_SEC;
    printf( "Times Cost : %.2f \n", time_duration );

     return  0;
}
int LineSearch( int *pData,  int n,  int key)
{
     int idx =  0;
     int times =  0;
     while(idx != n )
    {
         if(pData[idx] != key)
            times++, idx++;
         else
             return times;
    }

     return - 1;
}
int BinarySearch( int *pData,  int n,  int key)
{
     int center, left =  0, right = n -  1;
     int times =  0;

     while(left <= right)
    {
        center = (left + right) /  2;
         if (pData[center] > key)
        {
            times++, right = center -  1;
        }
         else
        {
             if (pData[center] < key)
                times++, left = center +  1;
             else
                 return times;
        }
    }
     return - 1;
}

/*
  Fibonaccian search for locating the index of "key" in an array "pData" of size "n"
  that is sorted in ascending order. See http://doi.acm.org/10.1145/367487.367496

  Algorithm description
  -----------------------------------------------------------------------------
  Let Fk represent the k-th Fibonacci number where Fk+2=Fk+1 + Fk for k>=0 and
  F0 = 0, F1 = 1. To test whether an item is in a list of n = Fm ordered numbers,
  proceed as follows:
  a) Set k = m.
  b) If k = 0, finish - no match.
  c) Test item against entry in position Fk-1.
  d) If match, finish.
  e) If item is less than entry Fk-1, discard entries from positions Fk-1 + 1 to n.
     Set k = k - 1 and go to b).
  f) If item is greater than entry Fk-1, discard entries from positions 1 to Fk-1.
     Renumber remaining entries from 1 to Fk-2, set k = k - 2 and go to b)

  If Fm>n then the original array is augmented with Fm-n numbers larger
  than key and the above algorithm is applied.
 */

int FibonacciSearch( int *pData,  int n,  int key)
{
     register  int k, idx, offs;
     static  int prevn = - 1, prevk = - 1;
     int times =  0;

     /*  Precomputed Fibonacci numbers F0 up to F46. This implementation assumes that the size n
     *  of the input array fits in 4 bytes. Note that F46=1836311903 is the largest Fibonacci
     *  number that is less or equal to the 4-byte INT_MAX (=2147483647). The next Fibonacci
     *  number, i.e. F47, is 2971215073 and is larger than INT_MAX, implying that it does not
     *  fit in a 4 byte integer. Note also that the last array element is INT_MAX rather than
     *  F47. This ensures correct operation for n>F46.
     */

     const  static  int Fib[ 47 +  1] = { 011235813213455891442333776109871597258441816765,
                                     1094617711286574636875025121393196418317811514229832040134626921783093524578,
                                     5702887922746514930352241578173908816963245986102334155165580141267914296,
                                     43349443770140873311349031701836311903, INT_MAX
                                   };

     /* find the smallest fibonacci number that is greater or equal to n. Store this
     * number to avoid recomputing it in the case of repetitive searches with identical n.
     */

     if(n != prevn)
    {
         register  int f0, f1, t;
         for(f0 =  0, f1 =  1, k =  1; f1 < n; t = f1, f1 += f0, f0 = t, ++k);

        prevk = k;
        prevn = n;
    }
     else
        k = prevk;

     /* If the sought value is larger than the largest Fibonacci number less than n,
     * care must be taken top ensure that we do not attempt to read beyond the end
     * of the array. If we do need to do this, we pretend that the array is padded
     * with elements larger than the sought value.
     */

     for(offs =  0; k >  0;  )
    {
        idx = offs + Fib[--k];

         /* note that at this point k  has already been decremented once */
         if(idx >= n || key < pData[idx])  // index out of bounds or key in 1st part
        {
            times++;
             continue;
        }
         else  if (key > pData[idx])
        {
             // key in 2nd part
            times++;
            offs = idx;
            --k;
        }
         else  // key==pData[idx], found
             return times;
    }

     return - 1// not found
}


  • 7
    点赞
  • 4
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

fovwin

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值