斐波那契查找

黄金分割点:是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比,其比值是一个无理数,用分数表示为(√5-1)/2,取其前三位数字的近似值是0.618

斐波那契数列:F(0)=1,F(1)=1, F(n)=F(n - 1)+F(n - 2)(≥ 2,∈ N*),随着数列项数的增加,前一项与后一项之比越来越逼近黄金分割的数值

 

原理

1.思路类似二分查找,前提:待查数列要求有序

2.仅仅改变了midIndex的位置,midIndex位于黄金分割点附近,即midIndex=left+F(k-1)-1

二分查找的midIndex = left + 1 / 2 * (right - left),斐波那契查找相当于把1 / 2替换为黄金分割数

 

对F(k-1)-1的理解:

1.通过斐波那契 数列F[k]=F[k-1]+F[k-2] 的性质,可以得到(F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1

该式说明:只要顺序表的长度为F[k]-1,则可以将表分为长度为F[k-1]-1和F[k-2]-1的两端。从而中间位置为 midIndex=left+F(k-1)-1

2.类似的每个子段也可以使用相同的方式分割

3.为什么要求顺序表长度n=F(k)-1 。进行-1操作,这是因为有序表数组位序从0开始的,是为了迎合位序从0开始。

假如待查找数组长度为F(k),不考虑midIndex的情况下,左边为F(k - 1),右边为F(k -2),考虑midIndex的情况下,要不左边是F(k-1) - 1或者右边是F(k - 2) - 1 ,逻辑不好写。

4.但顺序表长度n不一定刚好等于F[k]-1,所以需要将原来的顺序表长度n增加至F[k]-1,k值只要能使得F[k]-1恰好大于或等于n即可

 

思路

1.把一个斐波那契数列映射到原需要查询的数列,原数列进行划分的规则就是借助于一个斐波那契数列

2.通过将斐波那契数列中的每一项的值看成是一个数列中元素的个数,其中,斐波那契数列中的每一项减去1就代表原数列中的下标,因为原数列起始下标是从0开始的。

3.由于斐波那契数列中的每一项我们看成是一个数列的元素个数,而斐波那契数列中的每一项的值是固定了的,但它却不包含所有的值。所以我们需要将原需要查询的数列扩容到大小离斐波那契数列中最接近的那一个数。

4.原数组长度是不能改变的,我们只能创建一个新的数组来复制arr数组的值并复制最后一个元素添加到末尾

 

代码实现

import java.util.ArrayList;
import java.util.Arrays;

public class FibonacciSearch {
    
    public static int[] fibonacciSequence(int maxSize) {//获取斐波那契数列
        int[] f = new int[maxSize];
        f[0] = 1;
        f[1] = 1;
        for (int i = 2; i < maxSize; i++) {
            f[i] = f[i - 1] + f[i - 2];
        }
        return f;
    }

    public static ArrayList<Integer> circleFibonacciSearch(int[] array, int find) {
        int left = 0;
        int right = array.length - 1;
        int k = 0;//斐波那契数列中分割值的下标
        int f[] = fibonacciSequence(20);
        ArrayList<Integer> indexList = new ArrayList<>();//存放符合find的元素的索引
        if (array == null || array.length == 0) {
            return indexList;//数组为空
        } else if (array.length == 1) {
            if (find == array[0]) {//
                indexList.add(0);
            }
            return indexList;
        }
        while (array.length > f[k]) {//获取斐波那契数列中分割值的下标,分割值即temp数组的大小
            k++;
        }
        //创建大小为f[k]的temp数组
        int[] temp = Arrays.copyOf(array, f[k]);
        //f[k]若大于array.length,temp多余的部分用array[right]填充
        for (int i = array.length; i <= temp.length - 1; i++) {
            temp[i] = array[right];
        }
        while (find >= array[left] && find <= array[right]) {
            int midIndex = left + f[k - 1] - 1;
            int mid = temp[midIndex];
            //每次向左边查找就是将f[k-1]中的k换为k-1,每向右边查找一次就是将f[k-1]中的k换为k-2
            if (find < mid) {//向左查找
                right = midIndex - 1;
                //左方有f[k-1]个元素,可继续拆分f[k-1]=f[k-2]+f[k-3],即下次循环midIndex=left+f[(k-1)-1]-1
                k -= 1;
            } else if (find > mid) {//向右查找
                left = midIndex + 1;
                //右方有f[k-2]个元素,可继续拆分f[k-2]=f[k-3]+f[k-4],即下次循环midIndex=left+f[(k-2)-1]-1
                k -= 2;
            } else {//find == mid
                if (midIndex > right) {//mid取到了填充数组末尾的重复元素了
                    addIndex(indexList, right, array, find);
                    return indexList;
                } else {//midIndex <=right,mid没有取到填充数组末尾的重复元素
                    addIndex(indexList, midIndex, array, find);
                    return indexList;
                }
            }
        }
        return indexList;//find不存在
    }

    //封装方法,把等于find的元素的索引加入indexList
    public static void addIndex(ArrayList<Integer> indexList, int midIndex, int[] array, int find) {
        indexList.add(midIndex);//中间值本身的索引放入indexList中
        int temp = midIndex - 1;
        while (true) {//中间值向左寻找相同值的索引
            if (temp < 0 || array[temp] != find) {
                break;
            }
            indexList.add(temp--);
        }
        temp = midIndex + 1;
        while (true) {//中间值向右寻找相同值的索引
            if (temp > array.length - 1 || array[temp] != find) {
                break;
            }
            indexList.add(temp++);
        }
    }
}

 

posted @   半条咸鱼  阅读(215)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示