斐波那契查找算法
斐波那契数列,又称黄金分割数列,除前2个数之后,后面一个数等于前面2个数之和。如1,1,2,3,5,8,13,21… 定义为F(1)=1 F(2) =1 …F(n) = F(n-1) + F(n-2),当n值越大的时候约接近黄金比例0.618
斐波那契查找算法就是在这基础上实现,本质上跟二分查找思想一致,只是在找中值索引的时候使用了斐波那契的思想进行黄金分割。
实现步骤
- 根据需求创建一个斐波那契数列
- 根据原数组的长度找到最接近的一个斐波那契数列的长度
- 如果原数组长度小于斐波那契数列要求的长度则由原数组最后一个元素填充
- 根据斐波那契F(n) = F(n-1) + F(n-2) 拆分成F(n-1)左边 和 F(n-2)右边
- mid的计算公式 mid = low + F(k-1) -1 分割的左边界用于控制元素的范围 -1 是因为java数组从0开始 F(k-1) 则是F(n-1)即斐波那契切分出来左边的部分
- 如果目标值大于中值midVal 则说明需要从中值右边查找则是斐波那契分割的右边f(n-2)
- 如果目标值小于中值midVal 则说明需要从中值右边查找则是斐波那契分割的左边f(n-1)
- 递归查找结果分三种情况
- 没找到 分割到1个元素无法继续分割
- 找到了 mid的值落在正常原数组长度范围类直接返回
- 因为原数组不足斐波那契长度填充的情况,mid可能落在原数组长度之外,则直接返回原数组最后一个元素的索引即可
代码示例
public class FibonacciSearch {
public static void main(String[] args) {
int[] arr = {1, 8, 10, 89, 1000, 1234};
int index = fibonacciSearch(arr, 1234);
System.out.println("fibonacci index => " + index);
}
/**
* 创建一个斐波那契数列 非递归实现
*
* @param arr
* @return
*/
public static int[] createFibonacci(int[] arr) {
if (arr.length == 1) {
arr[0] = 1;
return arr;
} else if (arr.length == 2) {
arr[0] = 1;
arr[1] = 1;
return arr;
}
arr[0] = 1;
arr[1] = 1;
for (int i = 2; i <= arr.length - 1; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr;
}
public static int fibonacciSearch(int[] arr, int target) {
// 创建一个斐波那契数列
int[] fibonacci = createFibonacci(new int[20]);
// 低位索引
int low = 0;
// 高位索引
int high = arr.length - 1;
// 初始化斐波那契数列索引
int k = 0;
// 数组长度
int n = arr.length;
// 根据数组长度找到最接近斐波那契数列的k值 要找到根据数组长度来切割的点
while (n > fibonacci[k]) {
k++;
}
// 为满足斐波那契黄金分割数量要求 如果数组长度不足得需要将数组最后一个元素补齐一直到满足斐波那契分割的长度要求
// 如这里是列子数字长度是6 但最接近6的斐波那契数是8 所以需要补2位 补成{1,8, 10, 89, 1000, 1234,1234,1234} 这样才能使得斐波那契能分割
// 这里使用了Arrays.copyOf方法拷贝了一个新的数组并将长度补齐
int[] tempArr = Arrays.copyOf(arr, fibonacci[k]);
for (int i = arr.length; i < tempArr.length; i++) {
tempArr[i] = arr[arr.length - 1];
}
// 低位索引 低于 个高位索引 就一直循环
while (low <= high) {
// 找到斐波那契的黄金分割点 mid 第一次循环进入 low = 0 k=5
// mid = 0 + f[5-1] -1; mid = 0+f[4]-1 => 0 + 5 -1 = 4 相当于数组被分割成了0-4索引 一共有5个元素 后面8-5=3个元素 被分割成了一个斐波那契数列
// 第一次分割0-4 mid = 0 + f[5-1-1] - 1 ==> mid = 0 + 2 -1 => mid=1 数组被分成了0-1 和1-4 0-1有2个元素 1-4有3个元素 又被分成了一个斐波那契数列
// 最后-1是因为java数组从0开始
int mid = low + fibonacci[k - 1] - 1;
if (target < tempArr[mid]) {
high = mid - 1;
// 这里 k-- 是为了得到斐波那契数列k之前那的那个数从而得到长度进一步切分 这里k的算法与mid的求值方法有关
// 上面的mid中值算法把 f(k-1)放在了左边 f(k-2)放在了右边 所以移动的时候左边是k-1 右边是k-2
// 如 8 被切分成了 5-3 如果往左找 就是找前面5个元素 5个元整正好是k[4]
k--;
} else if (target > tempArr[mid]) {
low = mid + 1;
// 与往左找相反 如果往右找 的是k-2 因为斐波那契 f(k) = f(k-1) + f(k-2) 往左是f(k-1)右边则是f(k-2)
// 同样以第一次分切 8 被切分成了 5-3 右边是3 正好是f(3) f(5)全部 = f(4)左边 + f(3)右边
k -= 2;
} else {
// 这里判断mid 与arr.length-1 是因为 有可能我们数组是经过补足成斐波那契长度的数组 后面全部以最后一个元素填充
// 如果mid 落在了后面补足元素的索引上不能直接返回mid 因为mid已经超过了原来数组的索引
// 又因为 超过原数组的都是由最后一个元素补足的则直接返回最后一个元素索引也是一样的
if (mid <= arr.length - 1) {
return mid;
} else {
return arr.length - 1;
}
}
}
return -1;
}
}