简介
- Fibonacci查找是二分查找算法一种变形, 二分查找的中值为m
m = (start + end) / 2 = start + (end-start) / 2
- 二分查找每次都是折半查找, 即系数为1/2
- Fibonacci的取值是根据数组的黄金分割比进行分割
原理
- 假设我们有数组arr, 数组长度刚好为 F(n), 根据
F(n) = F(n-1) + F(n-2)
- 知道可以将数组分为长度为F(n-1), 和F(n-2)的两个子数组
- 但是这样还不够,还缺少了中值m, 所以切分成两个是不够的, 还需要切分多一个长度为1的数组,包含中值m
{F(n)} = {F(?)} + [1] + {F(?)}
F(n) - 1
= F(n-1) + F(n-2) - 1
= (F(n-1) -1) + (1) + (F(n-2) - 1)
A(n) = A(n-1) + 1 + A(n-2)
- 所以最终是要构造长度为 A(n) = F(n) -1的数组
- 然后不断的切分为长度分别为 A(n-1)= F(n-1) -1, 1 , A(n-2)= F(n-2) -1的子数组,去1对应的中值进行比较
- 于是得到中值公式
m = start + A(n-1)
- 然后进行比较,查找key对应的位置
- key > arr[m]
- 此时key位于 A(n-2)的区间
- 所以 n = n-2, start = m + 1;
- 继续查找
- key < arr[m]
- 此时key位于 A(n-1)的区间
- 所以 n = n-1, end = m – 1;
- 继续查找
- key = arr[m], 那么m 就是要查找的索引,结束
实现
- 首先给定的arr长度不一定是 A(n)=F(n) -1, 所以我们需要构造一个 长度为A(n) 的新数组,长度不够的补最大值
- 最后查找的时候,如果超过原有数组长度,那么直接取原数组最大的索引就行了
- 准备Fibonacci函数
- 填充原有数组
- 实现二分查找
- 改造
实现Fibonacci
function Fibonacci(n) {
var temp = [0, 1];
var i = 2;
while (i <= n) {
temp[i] = temp[i-1] + temp[i-2];
i++;
}
return temp[n];
}
- 由于我们要经常用这个函数,所以应该直接返回 temp数组,否则会影响效率,改造一下,根据数组长度返回Fibonacci的结果temp
// 根据 F(n-1) -1 < arrLength <= F(n) -1
function Fibonacci (len) {
var temp = [0, 1];
var i = 1;
while (len > (temp[i] -1 )) {
i++;
temp[i] = temp[i-1] + temp[i-2]
}
return temp
}
拓展数组
function expand(arr, len) {
var max = arr[arr.length -1];
var i = arr.length;
arr = arr.slice();
while (i < len) {
arr[i] = max;
i++;
}
return arr;
}
实现个二分查找
function Search(arr, key) {
var start = 0;
var end = arr.length - 1;
var m = 0;
while (start <= end) {
m = parseInt((start + end) / 2);
if (key > arr[m]) {
start = m + 1;
continue;
}
if (key < arr[m]) {
end = m - 1;
continue;
}
return m;
}
return -1;
}
改造
function FibonacciSearch(arr, key) {
var max = arr.length -1;
var F = Fibonacci (arr.length);
var n = F.length -1;
arr = expand(arr, F[n] -1); // A[n] = F[n] - 1;
var start = 0;
var end = max;
var m = 0;
while (start <= end) {
m = start + F[n-1] -1; // 1 A[n-1] = F[n-1] - 1;
if (key > arr[m]) {
start = m + 1;
n = n -2; //2
continue;
}
if (key < arr[m]) {
end = m - 1;
n = n -1;//3
continue;
}
return Math.min(m, max); // 最后的结果不能超过max
}
return -1;
}
总结
- 关于时间复杂度是O(logn), 这个我就不测试了。
- 不过平均性能来说比二分查找要好
- 如果key位于两端,就会比二分查找慢。
- ps: 本人也是刚好复习算法,写出来加深印象。