七大查找算法——斐波那契查找(四)

斐波那契数列

定义: 斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)

在数列:0、1、1、2、3、4、5、8…中,我们发现任意前两项之和,等于第三项,即 0 + 1 = 1、1 + 1 = 2、1 + 2 = 3、2 + 3 = 5。这样的数列就是斐波那契数列。而在这个数列中的数,就叫斐波那契数。

它和黄金分割有什么关系呢?我们发现,斐波那契数列越往后,前一项与后一项的比值原来越趋近于 “黄金分割” (即0.618)。比如:13 / 21 = 0.619… ; 21 / 34 = 0.617…; 34 / 55 = 0.618…;

斐波那契查找

斐波那契查找是基于 二分查找不懂二分查找的点这里!)改进的一种查找方式,同样要求查找的数据是 有序的。总体思想和二分查找相同,不同点是对于每次中间值的选取。二分查找每次是取正中间的值,使得原数组被划分为 0.5 :0.5 平均的两部分。斐波那契查找是选取黄金分割点,使得原数组被划分为 0.382 :0.618 的两部分。

实现前的细节问题

在具体说查找实现之前,我们明确两个问题:

1.为什么要将数组长度扩充到刚好等于某个斐波那契数 - 1 ?
2.问题1中的 - 1 是怎么来的?

问题1:我们要将数组每次划分成 0.382 :0.618 的两部分,但是数组的长度应该是个整数,如果直接用小数代入算式中,那被舍弃的小数将会使得误差越来越大,即你划分开来的两部分,离黄金分割要求的 0.618 越来越偏。而我们观察斐波那契数列,里面的数既符合整数的要求,而且也符合黄金分割的要求,所以我们将数组长度扩充,借助斐波那契数列帮我们完成分割。

问题2:我们举个例子,现在你有一个数组:[1,2,3,4,5]长度为5,正好是一个斐波那契数,我们要先选出一个值进行比较。那么如果比较完,不符合,需要进行划分,被选出且比较过的值,我们还需要再划分进来吗?当然不需要!这样只会造成浪费,重复比较。所以拿掉这个数后,剩下4个数,按照斐波那契数列,应该划分为2和3两部分,可是4个数,不好划分。
如果我们数组是[1,2,3,4]长度为4,那么选取完一个去比较后,剩下3个数,那么我们可以划分为 2 - 1 和 3 - 1两部分,这样他们依旧保持着某个斐波那契数 - 1的形式!
5 - 1(原数组) = (3 - 1)(划分后前面部分) +(2 - 1)(划分后后面部分)+ 1(拿出去比较的数)。

斐波那契划分示意图

具体实现步骤

1.构建一个斐波那契数列。(我们需要斐波那契数列,不伸手,自己造。)
2.寻找出大于要查找数组长度的斐波那契数。
3.将查找数组的长度扩容,使其长度=某个斐波那契数-1。扩容后,将原数组最后一个数复制,填满扩容后出现的空位。
4.开始像二分查找那样循环查找,但要注意每次选取的比较值方式是和二分查找不同的。
5.如果判断的值相等,要判断索引结果是否大于原数组的最大索引,如果大于,要返回的是原数组的最大索引。因为后面扩容的数据都是原数组最后一个数复制上去的。而且不能返回一个比要查找数组索引值之外的结果回去(除了-1表示找不到)。

复杂度分析

最坏情况下,时间复杂度为O( l o g 2 log_2 log2N),且其期望复杂度也为O( l o g 2 log_2 log2N)。

该查找mid = left + f[k-1] - 1;不涉及除法,如果该数列长度正好等于一个斐波那契数-1,那么理论情况要比有除法的二分查找法快。

适用情况:查找数组是有序的,且长度正好等于某个斐波那契数-1。

代码实现

此次实现是基于Java,jdk1.8。

1.构建一个斐波那契数列。我们将其定义为一个静态方法,方便调用。用循环很快就可以构建出来。

    public static int[] fib(int size) {
        int[] fib = new int[size];
        fib[0] = 1;
        fib[1] = 1;
        for (int i = 2; i < fib.length; i++) {
            fib[i] = fib[i - 1] + fib[i - 2];
        }
        return fib;
    }

2 . 寻找出大于要查找数组长度的斐波那契数。首先要调用构建斐波那契数列的方法,返回一个斐波那契数列。然后寻找出大于等于查找数组长度的斐波那契数。

	public int fibonacciSearch(int[] data, int target) {
		int[] f = fib(20);
        while (f[f.length - 1] < data.length) {
            f = fib(f.length * 2);
        }
        int k = 0;
        while (data.length > f[k] - 1) {
            k++;
        }   
	}

3 . 将查找数组的长度扩容,使其长度=某个斐波那契数-1。扩容后,将原数组最后一个数复制,填满扩容后出现的空位。

		int left = 0;
        int right = data.length - 1;
        int mid = 0;
        int[] temp = Arrays.copyOf(data, f[k]-1);
        //        后边多余的部分,用data[right]补充
        for (int i = right + 1; i < temp.length; i++) {
            temp[i] = temp[right];
        }

4 . 开始像二分查找那样循环查找,mid = left + f[k - 1] - 1;

		while (left <= right) {
            //            黄金分割,一共有f[k]-1个,左边是f[k-1]-1个,
            //            右边是f[k-1]-1个,所以中间元素的下标为mid = left + f[k-1]-1;
            //            比如[1,2,3,4],
            //            前面f[k-1]-1个,就是2个,后面f[k-2]-1个,就是1个,那么中间元素3的下标正好是f[k
            //            -1]-1+left=2+0=2
            //            f[k-1]-1 + f[k-2]-1 + 1(mid) = f[k-1]
            mid = left + f[k - 1] - 1;
            if (temp[mid] > target) {
                right = mid - 1;
                //                第一次分割后,左边剩下f[k-1]-1个,下一次分割f[k-1]=f[k-2]+f[k
                //                -3],所以k--;
                //                换个理解,现在是f[k-1],要变成f[k-2],那就f[k-1-1],
                k--;
            } else if (temp[mid] < target) {
                left = mid + 1;
                //                右边剩下f[k-2]-1个,下一次分割f[k-2]=f[k-3]+f[k-4],
                //                所以k-=2;
                //                换个理解,现在是f[k-1],要变成f[k-3],那就f[k-2-1],
                k -= 2;
            } else {
                //                如果找到了,要注意,因为temp后面可能是补了好几个data[right
                //                ]的,所以我们要返回第一个data[right]的下标,所以mid不能超过right。
                if (mid <= right) {
                    return mid;
                } else {
                    return right;
                }
            }
        }

5 . 判断一下找到的结果索引,是否大于原数组最大索引,大于则返回原数组最大索引作为结果。

                if (mid <= right) {
                    return mid;
                } else {
                    return right;
                }

完整代码

//斐波那契查找
//该查找mid = left + f[k-1] - 1;不涉及除法,如果该数列长度正好等于一个斐波那契数-1,那么理论情况要比有除法的二分查找法快。
//同样要求有序数列,也是对二分查找的一种改进,用黄金分割来拆分。复杂度分析:最坏情况下,时间复杂度为O(log2n),且其期望复杂度也为O(log2n)。
//黄金比例又称黄金分割,是指事物各部分间一定的数学比例关系,即将整体一分为二,较大部分与较小部分之比等于整体与较大部分之比,其比值约为1:0.618或1
// .618:1。
//斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89……
// .(从第三个数开始,后边每一个数都是前两个数的和)。随着斐波那契数列的递增,前后两个数的比值会越来越接近0.618
//斐波那契数就是斐波那契数列中的一个值,比如21在数列中,就是一个斐波那契数,22不在,就不是。

import java.util.Arrays;

public class FibonacciSearch {
    //    首先要构建一个斐波那契数列,因为我们要知道哪些数是一个斐波那契数。
    //    只有它是一个斐波那契数,才能使用黄金分割。比如21是,那么可以分割成8和13。
    public static int[] fib(int size) {
        int[] fib = new int[size];
        fib[0] = 1;
        fib[1] = 1;
        for (int i = 2; i < fib.length; i++) {
            fib[i] = fib[i - 1] + fib[i - 2];
        }
        return fib;    
    }

    public int fibonacciSearch(int[] data, int target) {
        //        先去获取一个斐波那契数列,初始默认值20,如果20个斐波那契数列的数都小于查找数组长度,那就获取个长度翻倍的斐波那契数列。
        int[] f = fib(20);
        while (f[f.length - 1] < data.length) {
            f = fib(f.length * 2);
        }
        int left = 0;
        int right = data.length - 1;
        int k = 0;
        int mid = 0;
        //        找到大于数组长度的斐波那契数
        //        为什么这里是f[k]-1呢?
        //        1.首先我们找mid时,就用掉一个,如果直接使用斐波那契数(比如k=4,f[k]=5),
        //        那么分割后应该是f[k-1]=3,f[k-2]=2,这是用掉的mid应该从前段还是后段扣掉?
        //        这将导致子序列的元素个数可能为f(k-1),f(k-1)-1,f(k-2),f(k-2)-1
        //        2.所以我们使用f[k]-1,这样用掉一个mid后,剩下f[k]-2,那么直接分割成f[k-1]-1,f[k-2]-1。
        while (data.length > f[k] - 1) {
            k++;
        }
        //        构建一个长度为斐波那契数-1的数组
        int[] temp = Arrays.copyOf(data, f[k]-1);
        //        后边多余的部分,用data[right]补充
        for (int i = right + 1; i < temp.length; i++) {
            temp[i] = temp[right];
        }
        while (left <= right) {
            //            黄金分割,一共有f[k]-1个,左边是f[k-1]-1个,
            //            右边是f[k-1]-1个,所以中间元素的下标为mid = left + f[k-1]-1;
            //            比如[1,2,3,4],
            //            前面f[k-1]-1个,就是2个,后面f[k-2]-1个,就是1个,那么中间元素3的下标正好是f[k
            //            -1]-1+left=2+0=2
            //            f[k-1]-1 + f[k-2]-1 + 1(mid) = f[k-1]
            mid = left + f[k - 1] - 1;
            if (temp[mid] > target) {
                right = mid - 1;
                //                第一次分割后,左边剩下f[k-1]-1个,下一次分割f[k-1]=f[k-2]+f[k
                //                -3],所以k--;
                //                换个理解,现在是f[k-1],要变成f[k-2],那就f[k-1-1],
                k--;
            } else if (temp[mid] < target) {
                left = mid + 1;
                //                右边剩下f[k-2]-1个,下一次分割f[k-2]=f[k-3]+f[k-4],
                //                所以k-=2;
                //                换个理解,现在是f[k-1],要变成f[k-3],那就f[k-2-1],
                k -= 2;
            } else {
                //                如果找到了,要注意,因为temp后面可能是补了好几个data[right
                //                ]的,所以我们要返回第一个data[right]的下标,所以mid不能超过right。
                if (mid <= right) {
                    return mid;
                } else {
                    return right;
                }
            }
        }
        return -1;
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
斐波查找算法是一种基于二分查找算法查找算法,它利用了斐波数列的特性来确定查找的位置。下面是斐波查找算法的设计步骤: 1. 首先,需要确定斐波数列的长度,使得它大于等于待查找数组的长度。假设斐波数列的长度为n,那么有:F(n) = F(n-1) + F(n-2),其中F(0) = 0,F(1) = 1。 2. 然后,需要将待查找数组扩展到长度为n,扩展的部分用原数组的最后一个元素填充。 3. 接着,需要定义两个指针:low和high。初始时,low指向待查找数组的第一个元素,high指向斐波数列中第一个大于等于n的元素的下标减1。 4. 然后,需要计算mid的值,mid的值为low加上斐波数列中第k-1个元素的值,其中k为满足F(k)-1 >= n的最小值。 5. 然后,比较待查找数组中第mid个元素和要查找的元素的大小。如果待查找数组中第mid个元素小于要查找的元素,则将low指向mid+1;如果待查找数组中第mid个元素大于要查找的元素,则将high指向mid-1;否则,找到了要查找的元素,返回mid。 6. 重复步骤4和步骤5,直到low大于high为止。 下面是一个Python实现的斐波查找算法的例子: ```python def fibonacci_search(arr, x): n = len(arr) fib_k2 = 0 # F(k-2) fib_k1 = 1 # F(k-1) fib_k = fib_k1 + fib_k2 # F(k) while fib_k < n: fib_k2 = fib_k1 fib_k1 = fib_k fib_k = fib_k1 + fib_k2 offset = -1 while fib_k > 1: i = min(offset+fib_k2, n-1) if arr[i] < x: fib_k = fib_k1 fib_k1 = fib_k2 fib_k2 = fib_k - fib_k1 offset = i elif arr[i] > x: fib_k = fib_k2 fib_k1 = fib_k1 - fib_k2 fib_k2 = fib_k - fib_k1 else: return i if fib_k1 and arr[offset+1] == x: return offset+1 return -1 ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值