斐波那契查找是二分查找的一个变体,与插值查找一样,也是对二分查找中mid的重新定义与计算
首先我们来说说黄金分割点,简单的说就是:假如有一段长为l的绳子,在绳子上的某一点d切断该绳子,得到两根绳子,较长的那一根长度为为l1,较短的为l2,若l1/l = l2/l1 ≈ 0.618,那么我们称d为黄金分割点。
我们把绳子类比到数组,把点d类比到mid。也就是说,我们需要找出数组中的黄金分割点作为mid。
而斐波那契数列刚刚好是一个非常适合作为寻找黄金分割点的工具!
斐波那契数列有两个特点
- f(k) = f(k - 1) + f(k - 2)
k > 2
① - f(k - 2) / f(k - 1) ≈ 0.618
k > 2
②
我们根据①可知:若所要查找的数组长度为f(k),则可以切分为:左半部分长度为f(k - 1),右半部分长度为f(k - 2)。根据②以及黄金分割点的定义可以知道如果按这样切分,那么这两部分的长度之比正是黄金分割点0.618!则此时mid可以定义为:mid = left + f(k - 1) - 1
若数组本身的长度并没有任何大于2的常数,使得f(k)与之匹配呢?
那么我们需要找到一个k,使得 数组长度 <= f(k),对数组进行扩容,多出来的位置的值用原始数组最后一个元素的值填充。
算法代码
import java.util.Arrays;
public class FibonacciSearch {
private static int[] myArr;//原始数组
private static int[] extendArr; //扩容后的数组
private static final int FIBONACCI_SEQUENCE_SIZE = 20;// 斐波那契数列长度
private static int[] fibonacciSequence = new int[FIBONACCI_SEQUENCE_SIZE];// 斐波那契数列
private int k = 2; // 原始数组扩容到fibonacciSequence[k],k >= 0
static {
initFibonacciSequence();
}
private FibonacciSearch() {
extendArray();
}
public static FibonacciSearch instance(int[] arr) {
myArr = arr;
return new FibonacciSearch();
}
/**
*
* @Title: initFibonacciSequence
* @Description: 初始化斐波那契数列
* @param: 参数说明
* @return: void 返回类型
*/
private static void initFibonacciSequence() {
fibonacciSequence[0] = fibonacciSequence[1] = 1;
for (int i = 2; i < FIBONACCI_SEQUENCE_SIZE; i++) {
fibonacciSequence[i] = fibonacciSequence[i - 1] + fibonacciSequence[i - 2];
}
}
/**
*
* @Title: extendArray
* @Description: 对数组进行扩容
* @return: void
*/
private void extendArray() {
int length = myArr.length;
// 找到一个k,使得 数组长度 <= f(k),对数组进行扩容
while (length > fibonacciSequence[k]) {
k++;
}
extendArr = Arrays.copyOf(myArr, fibonacciSequence[k]);
int extendArrLength = extendArr.length;
// 多出来的位置的值用原始数组最后一个元素的值填充
for (int j = length; j < extendArrLength; j++) {
extendArr[j] = myArr[length - 1];
}
}
/**
*
* @Title: fibonacciSearch
* @Description: 利用斐波那契数列的递归二分查找
* @param: @param left 当前数组的第一个元素的下标
* @param: @param right 当前数组的最后一个元素的下标
* @param: @param value 要查找的值
* @param: @param k fibonacciSequence[k]是当前数组的长度,fibonacciSequence[k - 1]是左半部分长度,fibonacciSequence[k - 2]是右半部分长度
* @param: @return 参数说明
* @return: int 返回类型
* @throws
*/
private int fibonacciSearch(int left, int right, int value, int k) {
// 如果当前数组不为空
if (left <= right) {
int mid = left + fibonacciSequence[k - 1] - 1;// 找到数组的中点下标mid
// 如果所要查找的值比mid下标对应的值小,那么要向数组的左半部分进行查找
if (value < extendArr[mid]) {
right = mid - 1;
k--;//见方法上对参数k的注释
return fibonacciSearch(left, right, value, k);
} else if (value > extendArr[mid]) {// 如果所要查找的值比mid下标对应的值大,那么要向数组的右半部分进行查找
left = mid + 1;
k-=2;//见方法上对参数k的注释
return fibonacciSearch(left, right, value, k);
} else {// 如果所要查找的值与mid下标对应的值相同
if(mid <= myArr.length - 1)return mid;//如果mid没有落入到扩容的部分(扩容部分的元素值全都是原始数组的最后一个元素的值)
return myArr.length - 1;//如果mid落入到扩容的部分(说明此时想查找的值是原始数组最后一个值),返回原始数组最后一个值的下标
}
}
// 若所要查找的数组为空,则返回-1
return -1;
}
public int fibonacciSearch(int value) {
return fibonacciSearch(0, extendArr.length - 1, value, k);
}
}
源码:github地址