首页 > 编程语言 > 详细

有序查找之:斐波那契(fibonacci)查找(C语言)

时间:2021-04-23 16:52:26      阅读:50      评论:0      收藏:0      [点我收藏+]
关于此算法的理解

斐波那契序列一些要牢记的知识点

序列从第3个值开始,其值等于前2个值的和

? 即某值下标为k,则F[k] = F[k-1] + F[k-2]

相临的序列的值 前面的值/后面的值会越来越接近0.618黄金分割点

为什么会有斐波那契查找法?

既然二分查找法可以找到一个序列的0.5的位置进行查找,那么斐波那契也可以使用它的黄金分割点0.618位置应用于序列查找(如果有白银分割点0.718那么也可能也会有白银查找法)

相比二分查找,该算法的优势是在求mid值的时候使用的只是加减法,在数量较大时会提高效率

斐波那契序列数组和待查找的数组有什么关联性?

根据待查询数组的查询长度(以下简称查询长度),生成斐波那契序列数组,且该序列的值一定要把查询长度包含进来,也就是说比如查询长度为n,则斐波那契序列只要满足最后一个下标的值F[k], n <= f[k]就可以,太长无用,序列第1个值是不是0无所库(本次实现直接手动创建fibonacci)

拉下来根据待查询数组的查询长度n匹配斐波那契序列,获取关键下标k,有两种情况:

? n 正好是有效的斐波那契序列中的数字n = F[k],直接返回所在下标k

? n 不是有效非斐波那契序列数字n < F[k],同样返回k,只是出现这种情况后原数组需要把查询长度扩充到k的长度才以进行查询,新扩充的位置用待查询数组的最后一个元素填充

F[k-1] 和 F[k-2] 两者的数量正好是被查询数组使用黄金分割点后的两部分

实现

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int fibonacci(int *, int, int);

int fibonacci(int *a, int n, int key)
{
   int low, high, mid, k;
    low = 1;
    high = n;
    // 为了代码简化直接手动创建fibonacci序列数组,如果代码动态生成则只要序列的最后1个值>=n即可
    // 此数组的值第1个元素是0还是1感觉影响不大
    int F[] = {0, 1, 1, 2, 3, 5, 8, 13}; // 这些值可以理解为使用fibonacci分割了查询长度

    k = 0;
    while (n > F[k]) { // 书为为n > F[k] - 1,但是感觉不需要,如果加上-1, n=8那么k最终为7 值13,完全用不上
        k++; // 返回下标7, 值为13, 8 < 10 < 13 向后取大
    }

    // 如果F[k]>n,则a数据扩容(扩容后的长度等于F[k] + 1,1为待查询数组不使用0下标)
    if (F[k] > n) {
        a = realloc(a, sizeof(int) * (F[k]+1));
        for (int i=n+1; i<=F[k]; i++) { // 11, 12, 13执行3次,填充新的数组元素
            a[i] = a[n];
        }
    }

    // 下面这段可以去掉注释看效果
    //if (F[k] > n) {
    //    printf("a数组查询长度需要扩展到 %d\n", F[k]);
    //    for (int i=0; i<=F[k]; i++) { // 下标14已经无效,这里为了看效果
    //        printf("a[%d]=%d\n", i, a[i]);
    //    }
    //}
    //else
    //    printf("a数组无需扩展\n");

    // 开始查找
    while (low <= high) {
        /* 关于下面这行的解释
         * low, 不用多说本次查询的起始下标
         * F[k-1], 因为F[k] = F[k-1] + F[k-2], F[k-1]/F[k]接近0.618,也就是要找的黄金分割点
         * -1, 因为F[k] = F[k-1] + F[k-2], 也就是说分为了两端,F[K-1]占到了F[k]的0.618也就是大半部分,
         *      F[k-2]占了小部分,这两部分的顺序不要纠结,只知道数量或长度即可,low + F[k-1]如果不减1,则
         *      会越界到F[k-2]的这一部分,因为low下标本身也要被查找
         */ 
        mid = low + F[k-1] -1;
        if (key < a[mid]) { // key在F[k-1]的部分,假设这部分在整体左边(好理解),F[k-1]可以只理解为这部分的数量)
            high = mid - 1;  // 移动high, 此时缩小的范围 low~high在F[k-1]下标范围内
            k = k - 1; // 下一轮查找的数组范围的fibonacci基数,和第1次找k一个道理 
        }
        else if (key > a[mid]) {
            low = mid + 1; // key在F[k-2]小部分,按照公式F[k] = F[k-1] + F[k-2],所以这部分的长度为F[k-2]
            k = k-2; // 下一轮查找F[k-2]这部分范围(找黄金分割点)的基数
        }
        else { // 查找成功
            if (mid <= n)
                return mid; // 在a扩展前的范围内匹配到
            else
                return n; // 在a扩展后的范围内匹配到,后面的值都是用n下标值填充的,所以返回n
        }
    }

    return 0;
}

int main(void)
{
    int data[11] = {0, 1, 16, 24, 35, 47, 59, 62, 73, 88, 99}; // 0下标未使用, 所以查询长度为10(1~10)
    // 拷贝data到a,让a支持扩展内存
    int *a;
    a = (int *)malloc(sizeof(int)*11);
    memcpy(a, data, sizeof(int)*11);

    printf("查找 59 的下标(0为未找到): %d\n", fibonacci(a, 10, 59)); 
    printf("查找 99 的下标(0为未找到): %d\n", fibonacci(a, 10, 99)); 
    printf("查找 15 的下标(0为未找到): %d\n", fibonacci(a, 10, 15)); 
    printf("查找 0 的下标(0为未找到): %d\n", fibonacci(a, 10, 0)); 
    // 如果进行下面这种查询,还需要做额外的修改,暂时不考虑这些场景
    //printf("在前4个元素 查找 59 的下标(0为未找到): %d\n", fibonacci(a, 4, 59)); 

    return 0;
}

output

[root@8be225462e66 c]# gcc fibonacci.c && ./a.out
查找 59 的下标(0为未找到): 6
查找 99 的下标(0为未找到): 10
查找 15 的下标(0为未找到): 0
查找 0 的下标(0为未找到): 0
[root@8be225462e66 c]#

有序查找之:斐波那契(fibonacci)查找(C语言)

原文:https://blog.51cto.com/sndapk/2726678

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!