斐波那契查找 简化理解


首先先看看二分查找,每次都是1/2进行折查。
试想一下如果不按照1/2进行折查,在某种情况下效率会不会更好些?
所以有人联想到了0.618(黄金分割),这个大自然神奇的数字运用在美术,摄影等等上会有不错的效果。
如果用到排序呢,那具体该怎么实现?
直接使用0.618未免太不优雅,然后有人提出了可以利用斐波那契数列的规律进行实现。

1、什么是斐波那契数列?
1、1、2、3、5、8、13、21、34……
斐波那契数列又被成为黄金分割数列,因为 前一项/后一项越来越趋近于0.618

由上面的数列,可以发现 除了前两项,后面每一项都是前两项的和,如3+5=8、8+13=21…

由此可以得到一下等式

F(n)=F(n-1)+F(n-2) (除了前两项)

2、斐波那契查找和斐波那契数列有什么联系?

斐波那契查找原理与前两种二分查找相似,仅仅改变了中间结点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列)

关于F(k)-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左右两部分,其中mid=low+F(k-1)-1,类似的每一子段可以用相同的方式分割。

代码实现中我都在旁边备注出了最难以理解的地方,例:

while (true){
    if (left > right){
        return -1;
    }
    int mid = left + f[k-1] - 1;
    int midValue = temp[mid];
    if (findValue < midValue){
        right = mid - 1;//原理同下
        k--;
    }else if (findValue > midValue){
        left = mid + 1;
        k -= 2; 
//初始k = 5 f[k]-1 = f[k-1]-1 + f[k-2]-1 这里相当于前一步的第二部分g(k) = f[k-2]-1
//g[k]-1 = g[k-1]-1 + g[k-2]-1  left = mid+1=left+f[k-1]-1 + 1  = 0+5-1+1=5
//g[k-1]-1 = f[k-1-2]-1-1 = f[k-3]-2
//这里因为left=mid+1了 要得到第二部分重新划分之后的第一部分,所以只需要k-=2
//新的mid=left+g[k-1]-1=mid+1+f[k-3]-2=mid+f[k-3]-1

    }else{
        if (mid <= right) {
            return mid;
        }else{
            return right;//如果mid索引大于right说明要查找的值是在我们扩充之后的索引,那么值跟原来的数组最后一个保持一致,就返回原数组最大索引值
        }
    }
public class FibonacciSearch {
    public static void main(String[] args) {
        int[] arr = {1,2,3,5,7,10};
        int i = fibSearch(arr, 0, arr.length - 1, 10);
        System.out.println(i);
    }

    //编写方法,获取斐波那契数列
    public static int[] fib(int length){
        int[] f = new int[length];
        f[0] = 1;
        f[1] = 1;
        for (int i = 2; i < length; i++) {
            f[i] = f[i-1] + f[i-2];
        }
        return f;
    }

    //方法,斐波那契查找
    public static int fibSearch(int[] arr, int left, int right, int findValue){
        int k = 0;
        int n = arr.length;
        int[] f = fib(n);
        while (n-1 > f[k] - 1){//循环到f[k]大于等于数组arr的长度
            k++;
        }
        //因为f[k]不一定刚好等于arr.length,可能更大,所以需要补齐
        int[] temp = Arrays.copyOf(arr, f[k]);
        //将temp数组补充的部分用arr数组的最后一个值填充
        for (int i = n; i < temp.length; i++) {
            temp[i] = arr[n-1];
        }

        while (true){
            if (left > right){
                return -1;
            }
            int mid = left + f[k-1] - 1;
            int midValue = temp[mid];
            if (findValue < midValue){
                right = mid - 1;
                k--;
            }else if (findValue > midValue){
                left = mid + 1;
                k -= 2; 
// k = 5 f[k]-1 = f[k-1]-1 + f[k-2]-1 这里相当于前一步的第二部分g(k) = f[k-2]-1
//g[k]-1 = g[k-1]-1 + g[k-2]-1  left = mid+1=left+f[k-1]-1 + 1  = 0+5-1+1=5
//g[k-1]-1 = f[k-1-2]-1-1 = f[k-3]-2
//这里因为left=mid+1了 要得到第二部分重新划分之后的第一部分,所以只需要k-=2 
// 新的mid=left+g[k-1]-1=mid+1+f[k-3]-2=mid+f[k-3]-1
                        
            }else{
                if (mid <= right) {
                    return mid;//如果mid索引小于最大right直接返回
                }else{
                    return right;//如果mid索引大于right说明要查找的值是在我们扩充之后的索引,那么值跟原来的数组最后一个保持一致,就返回原数组最大索引值
                }
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/Sciws/article/details/124059752