八-1, Java实现斐波那契查找算法,一个字,透彻!

8.4 斐波那契查找[要求有序]

8.4.1 斐波那契查找的相关知识点:

前置知识:

  1. 黄金比例: 整体与较大部分比值为 1:0.618 或者 1.618 :1
  2. 斐波那契数(黄金分割数列): 1,1,2,3,5,8,13,21,35…, 随着此数列的递增, 前后两个数的比例会越来越接近0.618.
  3. 什么是斐波那契数, 以及斐波那契数列的三种实现: Java实现斐波那契数的三种方式

斐波那契查找的特点:

  1. 斐波跟折半,差值查找一样, 都是要求数据是有序的(升序或降序)。同时, 斐波那契查找采用和二分查找/插值查找相似的区间分割策略,都是通过不断的分割区间缩小搜索的范围
  2. 斐波那契查找仅仅改变了开始查找点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1

性能分析:

最好: o(log2n)
最坏: o(log2n)

8.4.2 斐波那契查找算法的思想以及实现步骤

斐波那契查找算法的整体思想:

斐波那契数列找一个等于或第一个大于查找表中元素个数的数 F[k] (注意, 此时斐波那契数列是存放在数组中的, 所以会用F[]表示, 一定要注意跟F()的不同, 比如: F[6] =13 = F(7), F[0] = 1 = F(1)),
然后将原查找表的长度扩展为 F[k]-1 (如果要补充元素,则重复补充原查找表最后一个元素,直到原查找表个数中满足 F[k]-1;
扩展完成后进行斐波那契分割,即 F[k]-1 个元素分割为前半部分 F[k-1]-1 个元素,后半部分 F[k-2]-1 个元素, 剩下的一个元素作为开始查找结点mid, mid= low + F[k-1]-1;
通过不断比较mid, 递归分割区间, 找到符合要求的那个数.

斐波那契查找中常见的问题
Q1. 为什么待查找序列的长度扩充为 f[k]-1 ?
Q2. 为什么开始查找结点下标是 mid = low + [k-1]-1 ?

  • q1, q2我们可以一起回答, 首先由斐波那契数列的特性, f(k)=f(k-1)+f(k-2), 规定待排序列长度为f[k]-1 是为了格式上的统一,以方便递归或者循环程序的编写。
  • 表中的数据是f[k]-1个,使用mid值进行分割又用掉一个,那么剩下f[k]-2个。正好分给两个子序列,每个子序列的个数分别是f[k-1]-1与f[k-2]-1个,格式上与之前是统一的。不然的话,每个子序列的元素个数有可能是F[k-1],F[k-1]-1,F[k-2],F[k-2]-1个,写程序会非常麻烦。

有了以上的规定, 由上图, 我们可以很轻松的得出结论: mid = low + f[k-1]-1

Q3. 在后续的分割区间并查找比对时, 当 查找值key < temp[mid]时, k -= 1, 当 查找值key > temp[mid]时, k-=2; 为什么会这样呢?

  • 这个同样是斐波那契数列的特性*f(k) = f(k-1) + f(k-2)*所决定的, 要知道我们不总会一次区间分割就能找到需要的结点! 我们需要多次切割区间, 而每一段完整的区间总是由 f[k-1]个左区间和f[k-2]个右区间组成的!
  • 在一个循环中, 我们需要在多次分割区间进行查找, 某一个时刻, 在一个稍大一些的由左边区间和右边区间组成的序列中,key < temp[mid], 说明 key处于mid结点的左边, 我们会使用k -=1, high = mid-1转到这个序列的左区间中
  • 在下一次循环这个左区间又被继续划分为一左一右两个更短一些的序列, 如果这时候, key > temp[mid], 说明 key处于这个更短序列的右区间中, 我们会使用k-=2, low = mid+1转到这个区间中

我们就是这样不断往复, 不断往下划分区间, 不断在左区间和右区间中横跳来实现最终查找到目标值的索引.

斐波那契查找算法的具体实现步骤:

  1. 建立存放斐波那契数列的数组fibo;
  2. 判断待查找表的长度等于或第一个小于哪一个斐波那契数 f[k];
  3. 使用Arrays.copyOf(int[], length), 把查找表对应数组复制到一个临时数组, 长度为f[k]-1, 不够的用0填充;
  4. 如果查找表长度小于斐波那契数列中对应的元素 f[k]-1 的值,则用查找表的最后一个数填充至f[k]-1
  5. 根据斐波那契数列特点对查找表进行区间分隔,确定查找点 mid = left+F(n-1)-1;
  6. 判断中间值 arr[mid] 和目标值的关系,确定下一步策略:
    1. 如果目标值小于中间值,说明目标值在左区间。由于左区间长度为 F(n-1),因此 n 应该更新为 n-1,然后再次执行 4、5 两步;
    2. 如果目标值大于中间值,说明目标值在右区间。由于右区间长度为 F(n-2),因此 n 应该更新为 n-2,然后再次执行 4、5 两步;
    3. 如果目标值等于中间值,说明找到了目标值。但此时还需判别该目标值是原查找表中的元素还是填充元素
      1. 如果是原查找表中的元素,直接返回索引;
      2. 如果是填充元素,则返回原查找表的最后一个元素的索引,即 arr.length-1。(因为扩展数组是以原查找表最后一个元素来填充,如果目标值是填充元素,则说明原查找表最后一个元素值就是目标值)

完整代码实现:

package DataStrcture.SearchDemo;

import java.util.Arrays;

public class FibonacciSearch {
    public static void main(String[] args) {
        //待查找序列
        int[] arr = {1,3,5,6,8,13,45,67,133};
        int i = fiboSearch(arr, 133);
        System.out.println("斐波那契查找目标值133在数组中的下标为: "+i); 
    }


    //得到一个斐波那契数组
    public static int[] fibonacci(int n) {
        //初始化数组
        int[] fibo = new int[n];

        if (n >= 1) fibo[0] = 1;
        if (n >= 2) fibo[1] = 1;

        for (int i = 2; i < n; i++) {
            fibo[i] = fibo[i - 1] + fibo[i - 2];
        }
        return fibo;
    }
    //主方法

    /**
     * @param arr     待查找序列
     * @param findVal 查找的目标值
     * @return 返回目标值所在的下标
     */
    public static int fiboSearch(int[] arr, int findVal) {
        int low = 0;
        int high = arr.length - 1;
        int k = 0;
        int mid = 0;
        int maxsize = 20;

        //得到斐波那契数组
        int[] fibo = fibonacci(maxsize);

        //判断待查找序列长度应该为哪一个斐波那契数
        while (high > fibo[k] - 1) {
            k++;
        }
        //填充待排序列, 长度为f[k]-1
        把老数组复制到长度为 f[k]的新数组中
         超出老数组长度的用0填充
        int[] temp = Arrays.copyOf(arr, fibo[k]-1);
        //填充temp数组中为0的部分
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = arr[high];
        }
        System.out.println("填充了最后元素的临时数组: "+Arrays.toString(temp));
        //正式开始查找
        while (low <= high) {
            mid = low + fibo[k - 1] - 1;

            //不断比较不同区间内 findVal 与 temp[mid]的关系
            if( findVal < temp[mid]){
                high = mid -1;
                k -= 1;
            }else if( findVal > temp[mid]){
                low = mid+1;
                k -= 2;
            }else{
                //需要确定返回的是那个下标
                if(mid <= high){
                    return mid;
                }else{
                    return high;
                }
            }
        }
        return -1;
    }
}

在这里插入图片描述

posted @ 2022-05-26 20:31  青松城  阅读(270)  评论(3编辑  收藏  举报