斐波那契查找算法详解
说明
- 斐波那契查找算法核心思想类似于二分查找和插值查找,区别在于对标志值,即 mid 的设计算法不一样,二分查找直接重用中间值作为标杆,插值查找使用自适应确定mid,而斐波那契查找算法则使用黄金分割,使得mid总是处于查找数列的黄金分割点位置
- 因为斐波那契数列越到后边,相邻两数的比值越发接近0.618,也就是黄金分割比,因为可以巧妙的使用斐波那契数列寻找数组中的黄金分割点,即mid对应的下标
- 因此需要先构建一个斐波那契数列,可以使用递归的方法或者非递归的方式
- 使用斐波那契数列寻找数组的黄金分割点公式为: mid = low + f (k - 1) - 1,k为当前斐波那契数对应的索引
- 使用斐波那契数列查找,需要先将当前数组的长度构建为第一个比数组长度大的斐波那契数,这个数对应的索引就是 k ,可以使用循环的方法
- 将构建的新数组后边补零的位置替换为数组中的最后一个位置,即最大值
- 准备工作准备好后,就可以计算当前数组的黄金分割值,然后获取到当前黄金分割值对应的元素
- 将这个元素和要查找的元素进行比较,然后重置左右指针和重置后数组对应的黄金分割点
- 当查找完所有的元素后,如果没有找到,则返回 - 1
- 注意斐波那契数列的特性 即 当索引 > 2时,当前位置元素 = 前两个位置元素之和,而前两个位置元素之比刚好是满足黄金分割,正是基于这样的特性,才有公式 mid = low + f (k - 1) - 1
- 斐波那契查找算法不易理解,须慢慢体会
- 源码及详解见下
源码及分析
//斐波那契数列的最大长度
public static int maxSize = 20;
public static void main(String[] args) {
int[] arr = {1, 23, 45, 66, 67, 88, 90, 100};
int index = fisSearch(arr, 88);
System.out.println("index = " +index);
}
//构建斐波那契数列
public static int[] fis() {
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < f.length; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
/**
* 斐波那契查找算法实现
*
* @param arr 要查找的原始数组
* @param key 要查找的值
* @return 查找的结果
*/
public static int fisSearch(int[] arr, int key) {
//数组左侧索引
int low = 0;
//数组右侧索引
int high = arr.length - 1;
//比右侧索引大的第一个斐波那契数对应的索引
int k = 0;
//黄金分割点
int mid = 0;
//斐波那契数列
int[] f = fis();
//由数组最大值计算k
while (high > f[k] - 1) {
k++;
}
//因为f[k]的值可能大于数组的长度,因此需要给原数组扩容到长度 == f(k)
int[] tmp = Arrays.copyOf(arr, f[k]);
//调用copyOf方法后在扩容部分全部补了0,实际上需要补数组的最后一位
for (int i = high + 1; i < tmp.length; i++) {
tmp[i] = arr[high];
}
//使用while循环来查找需要找的数
while (low <= high) {
//先计算黄金分割点
mid = low + f[k - 1] - 1;
//判断黄金分割点的元素和要查找的元素的关系
//如果要查找的值在mid左边,重置high和k
if (tmp[mid] > key){
high = mid - 1;
k--;
//如果要查找的值在mid右边
}else if (tmp[mid] < key){
low = mid + 1;
k -= 2;
//否则找到该元素
}else {
if (mid <= high){
return mid;
}else {
return high;
}
}
}
//如果循环结束后还没有找到,说明没有
return -1;
}