斐波那契查找原理
斐波那契查找是一种在有序表中高效查找指定元素的算法,比折半查找要复杂一些,主要复杂在要多做不少准备工作。下面看它的工作流程:
- 计算并保存一个斐波那契序列的数组 F[ ] = {1, 2, 3, 5, 8, 13, 21},方便以后取值。
- 把有序数组的长度扩充到 nums. length = F[k] - 1,k 是满足条件的最小值,比如数组长度为15,那么就把它长度扩充到 F[6] - 1 = 20,所有在末尾添加的扩充元素都是原数组最后一个元素的复制。
- 找到 mid 元素,不断进行二分比较,直到找到目标元素为止,这一步的做法与折半查找很相似,仅仅是计算 mid 的公式从 (low + high) / 2改为 low + ( F[k-1] - 1)。
斐波那契查找的理解难点就一个:为什么需要把数组长度扩充到 F[k] - 1 而不是 F[k] 或者 F[k+1]?
其实这是为了能正确递归计算mid值,看下图可发现 F[k]-1 = (F[k-1] + F[k-2]) - 1 = (F[k-1]-1) + 1 + (F[k-2]-1),中间的1就是我们二分的锚点mid,如果目标在左区,数组长度就缩到(F[k-1]-1),如果在右区,数组长度就缩到(F[k-2]-1),否则就等于mid完成查找。而(F[k-1]-1)又能拆成(F[k-2]-1)+1+(F[k-3]-1),这样递归分割下去就能不断的缩小区间直至找到目标。
代码实现:
public int fibonacciSearch( int[] nums, int target) {
//设定一个斐波那契数组
int Fibonacci[] = {1, 2, 3, 5, 8, 13};
int k = 0;
while(nums.length >= Fibonacci[k]) {
k++;
}
//若nums数组长度不够Fibonacci[k]-1,用末尾元素补齐
int extra_length = Fibonacci[k] - nums.length - 1;
int newArr[] = new int[nums.length+extra_length];
for(int j=0; j<nums.length; j++) {
newArr[j] = nums[j];
}
for(int i=nums.length; i<newArr.length; i++) {
newArr[i] = nums[nums.length-1];
}
int low, high;
low = 0;
high = newArr.length-1;
//开始斐波那契查找
while(low <= high) {
int mid = low + Fibonacci[k-1]-1;
if(target < newArr[mid]) {
high = mid - 1;
k -= 1;
}else if(target > newArr[mid]) {
low = mid + 1;
k -= 2;
}else {
if(mid < nums.length) {
return mid;
}else {
return nums.length - 1;
}
}
}
return -1;
}
与折半查找的性能比较
二者理论效率半斤八两,时间复杂度都是log2n,有人说斐波那契查找比折半查找效率高,理由有2个:1.斐波那契查找只用到加减法,而折半查找计算mid时要除以2,除法很影响效率;2.如果目标在low->mid区,只需要判断一次,而如果在mid->high需要判断2次(需要先判断不在low->mid区,再判断在mid->high区),斐波那契查找的low->mid区更大(0.618>0.5),有更多的概率只需要判断一次,所以总体判断次数更少。
原因1看起来有理,可是现在的编译器只要遇到/2操作,都会优化为>>1,位运算比加减法只快不慢,所以原因1不成立。原因2也貌似有些道理,可是如果按照这个道理推理下去,把分割点设在0.99岂不是更好?可0.99明显是个垃圾分割点,二分力度很差,所以这个理由我也不认可。
斐波那契查找的时间复杂度还是O(log 2 n ),但是 与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小,但是还是得视具体情况而定。
本文转载自https://blog.csdn.net/weixin_42145502/article/details/99676008
本人在此基础上做了一点修改和取舍,修改仅代表本人观点,与原作者无关。