在正式介绍斐波那契查找之前,首先要知道什么是斐波那契数列:机器学习入坑者:面试被问到斐波那契数列怎么办?——附python和C++实现zhuanlan.zhihu.com
前面的文章分析了顺序查找、二分查找和插值查找,其中顺序查找的思想最简单,只需要将目标值和数据中每一个值进行比较即可;二分查找每次将区间长度缩减一半;插值查找按照目标值和边界值的关系确定区间分割方式。
斐波那契查找同样是查找算法家族中的一员,它要求数据是有序的(升序或降序)。斐波那契查找采用和二分查找/插值查找相似的区间分割策略,都是通过不断的分割区间缩小搜索的范围。
要点:斐波那契数和数据有什么关系?
斐波那契数列中(上表来自维基百科),从第三项开始,每一项都等于前两项之和:
上述性质可以用于数据区间分割,将一个长度为F(n)数组看做前后两半,前面一半长度是F(n-1),后面一半的长度是F(n-1)。正是这个想法将斐波那契数列和数组联系到一起,也是后面分析的基础:
其中n的取值是任意长度的,即对任意长度的数组都能找到对应的斐波那契数,下面将具体介绍斐波那契查找的步骤。
1、原理分析
斐波那契查找的整个过程可以分为:构建斐波那契数列;
计算数组长度对应的斐波那契数列元素个数;
对数组进行填充;
循环进行区间分割,查找中间值;
判断中间值和目标值的关系,确定更新策略;
1.1 构建斐波那契数列
这一步骤见前面的文章,构建斐波那契数列如下:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
1.2 计算数组长度对应的斐波那契数列元素个数
假设手中的数据如下:
[1, 2, 4, 6, 7, 9, 13,
16, 17, 21, 23, 25, 27,
31, 45, 56, 58, 61, 65,
67, 73, 75, 88, 93, 102]
可知上述数据共25个元素,不对应1.1节中的斐波那契数列中任何F(n),这种情况是很常见的。此时,策略是采用“大于数组长度的最近一个斐波那契数值”。比如当前数组长度为25,斐波那契数列中大于25的最近元素为34。
1.3 对数组进行填充
确定了斐波那契数值后,就要进行数值填充,即将数组从25个元素填充到34个。填充时,将第26到34个元素均采用第25个元素值进行填充,即最大值填充。
1.4 循环进行区间分割,查找中间值
这一个步骤与前面介绍的二分查找和插值查找相似,都是不断的缩小搜索区间,进而确定目标值的位置。区间分割公式如“要点”所述,每次分割中间位置的计算如下:
此时数组被分割为左右两个区间,左边区间含有F(n-1)个元素,-1是因为下标从0开始(比如F(1)表示两个元素)。
1.5 判断中间值和目标值的关系,确定更新策略
中间值和目标值有三种大小关系,分别对应三种处理方式:相等,则查找成功,返回中间位置即可;
中间值小于目标值,则说明目标值位于中间值到右边界之间(即右区间),右区间含有F(n-2)个元素,所以n应该更新为n=n-2;
中间值大于目标值,这说明目标值位于左边界和中间值之间(即左区间),左区间含有F(n-1)个元素,所以n应更新为n=n-1;
2、python实现
(1)实现过程中,如果数据是列表,则必须首先进行深度复制,因为填充过程会更改原始数据;
(2)因为预先不知道需要多少个斐波那契数值,所以使用max_size先构造一个较多斐波那契数值;
import copy
def fibonacci_search(ord_data, target_value, max_size):
"""Args:ord_data: ordered datatarget_value: value that be searchedmax_size: max size of fibonacci number"""
# 0:首选深度复制数据
D = copy.deepcopy(ord_data)
# 1:构建斐波那契数列
F = [0, 1] + [fibonacci(i) for i in range(2, max_size)]
# 2:计算数据长度对应斐波那契数列元素
index = 0
while(F[index]
index += 1
# 3:对数据进行填充
for i in range(len(D), F[index]):
D.append(D[len(D)-1])
# 4:对区间不断分割
left = 0
right = F[index]
while left <= right and index>0:
# 计算分割位置,左区间长度为F(n-2),右区间长度为F(n-1)
# -1是因为下标从0开始,即D[0]到D[20]对应21个元素
mid = left + F[index-1] - 1
# 判断中间值和目标值的关系
if D[mid] == target_value:
# 此时搜索到的目标值是填充的值
if mid > len(ord_data):
return len(ord_data)-1
else:
return mid
elif D[mid] < target_value:
left = mid + 1
index = index - 2
elif D[mid] > target_value:
right = mid - 1
index = index - 1
return None
3、C++实现
int fibonacciSearch(vectordata, int maxSize, int targetValue) {
// 1:构建斐波那契数列vectorfiboArray(maxSize, 0);
int oriLength = data.size();
for (int i = 0; i < maxSize-1; i++) {
fiboArray[i+1] = fibonacci(i);
// printf("%d ", fiboArray[i]);}
// 2:计算数据长度对应斐波那契数列元素int index = 0;
while (fiboArray[index]
{
index++;
}
// printf("%d %d \n", index, fiboArray[index]);
// 3:对数据进行填充for (int i = data.size(); i < fiboArray[index]; i++) {
data.push_back(data[data.size()-1]);
// printf("%d ", data[i]);}
// 4:对区间不断分割int left = 0;
int right = data[data.size() - 1];
while (left<=right && index>=0)
{
// 计算中间位置int mid = left + fiboArray[index - 1] - 1;
// 对三种情况分别处理// 目标值搜索成功if (data[mid] == targetValue) {
// 如果mid比原始数据长度大,则说明mid位置为填充的元素if (mid >= oriLength) { return oriLength - 1; }
else { return mid; }
}
// 目标值在中间值左边,更新右边界else if (data[mid]>targetValue) {
right = mid - 1;
index = index - 1;
}
// 目标值在中间值右边,更新左边界else {
left = mid + 1;
index = index - 2;
}
}
// 搜索失败return -1;
}
4、总结
折半查找需要进行除法,插值查找需要进行更复杂的乘法和除法,而斐波那契查找只需要使用加法和减法,在数据量较大时优势更明显。
参考: