首先先看看二分查找,每次都是1/2进行折查。
试想一下如果不按照1/2进行折查,在某种情况下效率会不会更好些?
所以有人联想到了0.618(黄金分割),这个大自然神奇的数字运用在美术,摄影等等上会有不错的效果。
如果用到排序呢,那具体该怎么实现?
直接使用0.618未免太不优雅,然后有人提出了可以利用斐波那契数列的规律进行实现。
1、什么是斐波那契数列?
1、1、2、3、5、8、13、21、34……
斐波那契数列又被成为黄金分割数列,因为 前一项/后一项越来越趋近于0.618
由上面的数列,可以发现 除了前两项,后面每一项都是前两项的和,如3+5=8、8+13=21…
由此可以得到一下等式
F(n)=F(n-1)+F(n-2) (除了前两项)
2、斐波那契查找和斐波那契数列有什么联系?
斐波那契查找原理与前两种二分查找相似,仅仅改变了中间结点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列)
关于F(k)-1
由斐波那契数列可知,F(k)=F(k-1)+F(k-2),那F(k)-1=(F(k-1)-1)+(F(k-2)-1)+1,所以数组长度只要满足 F(k)-1,就可以将数组分为F(k-1)-1和F(k-2)-1左右两部分,其中mid=low+F(k-1)-1,类似的每一子段可以用相同的方式分割。
代码实现中我都在旁边备注出了最难以理解的地方,例:
while (true){ if (left > right){ return -1; } int mid = left + f[k-1] - 1; int midValue = temp[mid]; if (findValue < midValue){ right = mid - 1;//原理同下 k--; }else if (findValue > midValue){ left = mid + 1; k -= 2; //初始k = 5 f[k]-1 = f[k-1]-1 + f[k-2]-1 这里相当于前一步的第二部分g(k) = f[k-2]-1 //g[k]-1 = g[k-1]-1 + g[k-2]-1 left = mid+1=left+f[k-1]-1 + 1 = 0+5-1+1=5 //g[k-1]-1 = f[k-1-2]-1-1 = f[k-3]-2 //这里因为left=mid+1了 要得到第二部分重新划分之后的第一部分,所以只需要k-=2 //新的mid=left+g[k-1]-1=mid+1+f[k-3]-2=mid+f[k-3]-1 }else{ if (mid <= right) { return mid; }else{ return right;//如果mid索引大于right说明要查找的值是在我们扩充之后的索引,那么值跟原来的数组最后一个保持一致,就返回原数组最大索引值 } }
public class FibonacciSearch {
public static void main(String[] args) {
int[] arr = {1,2,3,5,7,10};
int i = fibSearch(arr, 0, arr.length - 1, 10);
System.out.println(i);
}
//编写方法,获取斐波那契数列
public static int[] fib(int length){
int[] f = new int[length];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < length; i++) {
f[i] = f[i-1] + f[i-2];
}
return f;
}
//方法,斐波那契查找
public static int fibSearch(int[] arr, int left, int right, int findValue){
int k = 0;
int n = arr.length;
int[] f = fib(n);
while (n-1 > f[k] - 1){//循环到f[k]大于等于数组arr的长度
k++;
}
//因为f[k]不一定刚好等于arr.length,可能更大,所以需要补齐
int[] temp = Arrays.copyOf(arr, f[k]);
//将temp数组补充的部分用arr数组的最后一个值填充
for (int i = n; i < temp.length; i++) {
temp[i] = arr[n-1];
}
while (true){
if (left > right){
return -1;
}
int mid = left + f[k-1] - 1;
int midValue = temp[mid];
if (findValue < midValue){
right = mid - 1;
k--;
}else if (findValue > midValue){
left = mid + 1;
k -= 2;
// k = 5 f[k]-1 = f[k-1]-1 + f[k-2]-1 这里相当于前一步的第二部分g(k) = f[k-2]-1
//g[k]-1 = g[k-1]-1 + g[k-2]-1 left = mid+1=left+f[k-1]-1 + 1 = 0+5-1+1=5
//g[k-1]-1 = f[k-1-2]-1-1 = f[k-3]-2
//这里因为left=mid+1了 要得到第二部分重新划分之后的第一部分,所以只需要k-=2
// 新的mid=left+g[k-1]-1=mid+1+f[k-3]-2=mid+f[k-3]-1
}else{
if (mid <= right) {
return mid;//如果mid索引小于最大right直接返回
}else{
return right;//如果mid索引大于right说明要查找的值是在我们扩充之后的索引,那么值跟原来的数组最后一个保持一致,就返回原数组最大索引值
}
}
}
}
}