查找算法:斐波拉契查找

1,斐波拉契查找基本介绍

  • 黄金分割点:是把一条线段分为两部分,使得一部分与全程之比等于另一部分跟这部分之比,比例近似于0.618,称为黄金分割点
  • 斐波那契数列{1, 1, 2, 3, 5, 8 ... n, m, m + n}发现两个相邻数的比例,无限接近于0.618
  • 斐波那契查找,依旧基于数组是有序数组,并使数组长度与斐波那契数组元素相匹配,之后类似于二分查找方式,以斐波那契数组的数组特性f[k] = f[k - 1] + f[k - 2],对目标数组取中值middle = low+ f[k - 1] - 1后再进行分段查找
    在这里插入图片描述
  • 与二分查找和插值相比,重点依旧是取中值部分求值方式不同;另外效果与二分查找基本一致,网上有分析称斐波那契查找是加减运算,速度会高于二分查找
  • 斐波拉契查找流程分析
    • 首先初始化一个斐波拉契数组,数组长度可以初始化为20,尽量支持扩容
    • 用原数组长度去匹配到斐波拉契数组的元素值,获取到f[k] - 1 < array.length最近的数据作为原始数组在斐波拉契查找法中的真实长度,具体可参考示意图
    • 对原数组进行拷贝,拷贝目标数组的长度为f[k] - 1,并将多与原数组长度的新加数组部分数据值修改为原数组最大值,此时数组构建完成,可以开始查找
    • 斐波拉契查找中值公式:middle = low + f[k - 1] - 1;其中low表示左侧最小索引,f[k - 1] - 1表示middle值的左侧数组长度
    • 公式推导如下:数组长度 f[k] - 1 = f[k - 1] + f[k - 2] - 1,即f[k] - 1 = f[k - 1] - 1 + 1 + f[k - 2] - 1;此时可以将f[k - 1] - 1理解为数组中值的左侧部分数组,f[k - 2] - 1理解为数组中值的右侧部分数组,中间的1即表示middle索引部分,也就是中值数据
    • 获取到中值索引后,对中值数据与查找数据进行比对,如果小于该数据,则向左查找;如果大于该数据,则向右查找;如果相等,则根据当前中值索引是否大于原数组的最大索引判断返回当前索引还是最大索引
    • 向左查找:此时中值数据左侧数组部分长度为f[k - 1] - 1,继续推导公式f[k - 1] - 1 = f[k - 2] - 1 + 1 + f[K - 3] - 1,以f[k - 1] - 1继续为完整数组来看,则中值的左侧部分应该为f[k - 2] - 1,即f[k - 1 - 1] - 1,进入下一轮循环,此时k--
    • 向右查找:同上一步,中值的右侧部分应该为f[k - 3] - 1,即f[k - 2 - 1] - 1,此时k -= 2

2,斐波拉契查找原理示意图

  • 初始化数组:int[] array = {1, 2, 3, 4, 5, 6};
  • 初始化斐波拉契数组:int[] fbl = {1, 1, 2, 3, 5, 8};
  • 拷贝原数组后:array = {1, 2, 3, 4, 5, 6, 6},(array.length = fbl[k] - 1), 数组长度为fbl[k] - 1,此时k=5,则array长度应该为7
  • 则数组的的各部分组成如下
    在这里插入图片描述
  • 此时查找数据,如果落于middle左侧,则k--;落于middle右侧,则k-=2

3,代码实现

package com.self.datastructure.search;

import java.util.Arrays;

/**
 * 斐波拉契查找法
 *
 * @author pj_zhang
 * @create 2020-03-14 21:50
 **/
public class FibratcheSearch {

    public static void main(String[] args) {
        int[] array = {1, 12, 23, 34, 55, 78, 156, 765, 873, 987};
        System.out.println(fibratcheSearch(array, 874));
    }

    /**
     * 斐波拉契查找流程
     * * 先初始一个长度为20的斐波拉契数组备用, 数组长度如果超过fbl[19], 可以进行扩容
     * * 用数组长度去匹配合适的斐波拉契数值值, 作为原始数组斐波拉契查找法中的真实长度
     * * 对原始数组进行拷贝, 拷贝后的长度 = (斐波拉契的数值值 - 1), 多余原始数组部分初始化为0
     * * 对多余部分初始化为0进行修改, 修改为数组的最大值, 保证数组有序
     * * 数据初始化完成后, 继续进行数据查询, 数据查找与二分法基本一致, 只是middle的取值方式不一致
     * * 斐波拉契查找中: middle = left + F[k - 1] - 1;
     * * 其中F数组表示斐波拉契数组, k表示数据匹配到的斐波拉契数组下标, k对应长度即原始数组拷贝后的长度
     * * 根据斐波拉契查找算法补全位后, 原数组长度为 f[k] - 1
     * * 因为 f[k] = f[k - 1] + f[k - 2]
     * * 所以 f[k] - 1 = f[k - 1] + f[k - 2] - 1
     * * 即 f[k] - 1 = f[k - 1] - 1 + 1 + f[k - 2] - 1
     * * f[k - 1] - 1: 表示middle左侧数组长度
     * * 1: 表示middle所在位置
     * * f[k - 2] - 1: 表示middle右侧数组长度
     *
     * @param array 原数组
     * @param target 需要查找的值
     * @return 返回索引
     */
    public static int fibratcheSearch(int[] array, int target) {
        int left = 0; // 左索引
        int right = array.length - 1; // 右索引
        // 数组长度匹配到的斐波拉契数组下标
        // 该下标对应值为拷贝后的数组长度
        int k = 0;
        // 初始化斐波拉契数组
        int[] fbl = initFbl(20);
        // 用数组长度匹配到斐波拉契数组的对应元素, 比如数组长度为7, 则匹配8; 为10, 匹配13; 依次类推
        // 简单斐波拉契数据: {1, 1, 2, 3, 5, 8, 13, 21}, 即从1开始, 后一个数为前两个数之和
        for (;fbl[k] - 1 < array.length;) {
            // 这部分可以添加扩容逻辑,
            // 20表示初始化长度, 如果k为20依旧小于, 则进行扩容,
            // 扩容可以以array长度进行算法匹配, 求出大概索引位置进行扩容
            // 也可以类似于集合扩容, 进行1.5或者2倍扩容
            k++;
        }
        // 拷贝原数组为斐波拉契查找需要的长度
        int[] temp = Arrays.copyOf(array, fbl[k] - 1);
        // 数组长度增加后, 增加部分数据值初始化为0, 修改值统一为最大值, 保证数组有序
        for (int i = right + 1; i < temp.length; i++) {
            temp[i] = temp[right];
        }

        // 原数组和斐波拉契数组全部初始化完成后, 可以进行数据查找
        // 获取到middle值: middle = left + F[k - 1] - 1;
        for (;left <= right;) {
            // fbl[k]表示当前数组的长度, 如果已经循环多次, 依旧表示查找区间的数组长度
            // 例: 数组长度为13, fbl[k]=13, k=6, left=0, 则middle=7,
            //     此时向左继续查找, 则right=6, k=5, fbl[k]=7;
            // 对于斐波拉契查找法, 中值索引的选择就是以斐波拉契数组的前一个数为基本参考
            // 因此, 此时 midlle 取值就是以fbl[k - 1]作为基本参考
            // 以斐波拉契数组的组成方式,
            //  * middle左侧的数组长度是fbl[k - 1] - 1, 中值索引参考为fbl[k - 1 - 1]
            //  * middle右侧的数组长度是fbl[k - 2] - 1, 中值索引参考为fbl[k - 2 - 1]
            int middle = left + fbl[k - 1] - 1;
            if (temp[middle] > target) { // 向左继续找
                // 如果中值索引对应值小于目标值, 则向左侧继续寻找
                // 此时右侧索引变成中值索引的左侧索引
                right = middle - 1;
                // 当前循环没有匹配到, 且匹配到左侧, 需要进行下一轮循环继续匹配
                // 则下一轮循环的middle就是以fbl[k - 1]为完整数据进行求中值处理
                // 则对于左侧 middle 的参考系为fbl[k - 1 - 1]
                // 所以此时k应该减1
                k--;
            } else if (temp[middle] < target) { // 向右继续找
                // 如果中值索引对应值大于目标值, 则向侧继续寻找
                // 此时左侧索引变为中值索引的右侧索引
                left = middle + 1;
                // 当前没有匹配到, 且匹配到右侧, 需要进行下一轮循环匹配
                // 此时右侧数组的长度为fbl[k - 2]
                // 对于右侧数组来说, 中值索引参考应为fbl[k - 2]的前一个数即fbl[k - 1 - 2]
                // 此时k应该减2
                k -= 2;
            } else { // 相等, 返回索引
                return middle > right ? right : middle;
            }
        }
        return -1;
    }

    /**
     * 初始化斐波拉契数组
     *
     * @return
     */
    private static int[] initFbl(int size) {
        int[] array = new int[size];
        array[0] = 1;
        array[1] = 1;
        for (int i = 2; i < size; i++) {
            array[i] = array[i - 1] + array[i - 2];
        }
        return array;
    }

}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值