实现插值和斐波那契查找 实验
1.1 实验内容
所谓查找(Search)又称检索,就是在一个数据元素集合中寻找满足某种条件的数据元素。关于有序表的查找,有折半查找、插值查找、斐波那契查找等,它们的原理和实现方法各有不同,对不同数据的处理也各有优劣。
查找在计算机数据处理中是经常使用的操作。查找算法的效率高低直接关系到应用系统的性能。本次实验是在折半查找的代码基础上,实现插值查找和斐波那契查找,并比较不同的数据这三种方法的查找效率,得出初步结论。
1、插值查找
插值查找的算法思想同折半查找类似,区别在于求中间位置的公式,其求中间点的公式为:
mid = low + (high - low) * (k - elem[low]) / (elem[high] - elem[low])
其中,k为给定值,elem[low]和elem[high]分别为查找区间中具有最小关键字和最大关键字的数据元素,它们分别在查找区间的两端。插值算法将折半查找的比例参数改进为自适应的根据关键字在整个有序表中所处的位置,让mid值的变化更靠近关键字key。
2、斐波那契查找
斐波那契查找也是基于有字表的逐步缩小查找区间的查找方法。该方法的查找区间端点和中间点都与斐波那契数列有关。斐波那契数列的定义为:1,1,2,3,5,8,13,…。即价1)=1,f(2)=1,fi)=fi-l)+f(i-2)(当i>2时)。
斐波那契查找的理念:让mid保持在数组的黄金分割点处,mid前面长度为F[K-1]-1,后面长度为F[K-2]-1,数组总长度为F[K]-1,mid在黄金分割点。
1.2文件结构、开发环境等说明
开发环境版本 | VisualStudio 2022 | 工程文件名 | ex4_three_search.sln |
头文件个数 | 4个 | 源程序文件个数 | 1个 |
文件名 | 文件类型 | 功能简介 | 备注 |
binsearch.h | 头文件 | 折半查找算法实现 | |
FibonacciSearch.h | 头文件 | 斐波那契查找算法实现 | |
InsertSearch.h | 头文件 | 插值查找算法实现 | |
Assistance.h | 头文件 | 辅助软件包 | |
test.cpp | 源文件 | 测试文件 |
1.3 实现技术
1、插值查找算法
elem[]数组中存储了有序表中的n个数据,n个数据元素按其关键字从小到大排列。并在开始时,设查找区间的下限low-0,上限high=n-1。如果low>high,则表示查找失败,返回-1:否则进入while循环,进行查找。先求出查找区间分割处的数据元素下标mid = low + (high - low) * (k - elem[low]) / (elem[high] - elem[low]) ,用该数据元素的关键字elem[mid]与给定值key进行比较,比较的结果经过多个if-else判断语句:
①若elem[mid]==key,则查找成功,报告成功信息并返回其下标mid
②若elem[mid]<key,则说明如果数据表中存在要找的数据元素,该数据元素一定在mid的右侧,可把查找区间缩小到数据表的后半部分(low=mid+1),再继续进行插值查找。
③若elem[mid]>key,则说明如果数据表中存在要找的数据元素,该数据元素一定在mid的左侧。可把查找区间缩小到数据表的前半部分(high=mid-1)再继续进行查找。
template<class ElemType>
int InsertSearch(ElemType elem[], int n, ElemType key, int &count3)
// 操作结果: 迭代算法,在有序表中查找其关键字的值等于key的元素,如查找成功,则返回此元素的序号,否则返回-1
{
int low = 0, high = n - 1;int mid;
while (low <= high)
{
count3++;
if (elem[high] - elem[low] != 0)
mid = low + (high - low) * (key - elem[low]) / (elem[high] - elem[low]);
else
return -1;
if (key == elem[mid])
{
count3++;
return mid;
}
else if (key <= elem[mid])
{
high = mid - 1;
count3++;
}
else
{
low = mid + 1;
count3++;
}
}
return -1;
}
2、斐波那契算法
elem[]数组中存储了有序表中的n个数据,n个数据元素按其关键字从小到大排列。并在开始时,设查找区间的下限low-0,上限high=n-1。利用for循环生成斐波那契数列,并存储在fib[]数组中。while循环找到有序表元素个数在斐波那契数列中最接近的最大数列值,且如果n不满足斐波那契数列中某个元素的值,则用elem[high]补全有序表,后面的算法思想与折半查找类似:
1)如果查找区间长度小于(low>high),则表示查找失败,返回-1:否则继续以下步骤。
2)根据斐波那契数列求出查找区间中某位置的数据元素下标mid=f(k-1)-1
3)区间中间位置的数据元素的关键字elem[mid]与给定值key进行比较,比较的结果有以下三种可能。
①若elem[mid]==key,则查找成功,报告成功信息并返回其下标mid
②若elem[mid]<key,则说明如果数据表中存在要找的数据元素,该数据元素一定在mid的右侧,可把查找区间缩小到数据表的后半部分,得到的子表的长度正好为f(k-2)-1,再继续进行斐波那契查找。
③若elem[mid]>key,则说明如果数据表中存在要找的数据元素,该数据元素一定在mid的左侧。可把查找区间缩小到数据表的前半部分,得到的子表的长度正好为f(k-1)-1,再继续进行斐波那契查找。
//斐波那契查找
template<class ElemType>
int FibonacciSearch(ElemType elem[], int n, ElemType key, int &count2)
{
int low = 0, high = n - 1;
int mid, fib[MAXSIZE], k = 0;
fib[0] = fib[1] = 1;
for (int i = 2; i < MAXSIZE; i++)
fib[i] = fib[i - 1] + fib[i - 2];
//找到有序表元素个数在斐波那契数列中最接近的最大数列值
while (high > fib[k] - 1)
k++;
//补齐有序表
for (int i = n; i <= fib[k] - 1; i++)
elem[i] = elem[high];
while (low <= high)
{
count2++;
mid = low + fib[k - 1] - 1;//根据斐波那契数列进行黄金分割
if (key == elem[mid])
{
count2++;
if (mid <= n - 1)
{
count2++;
return mid;
}
else//说明得到的数据元素是补全值
{
return n - 1;
count2++;
}
}
if (elem[mid] > key)
{
count2++;
high = mid - 1;
k = k - 1;
}
if (elem[mid] < key)
{
count2++;
low = mid + 1;
k = k - 2;
}
}
return -1;
}
1.4测试结果
1、有序表数据个数n较大,且分布比较均匀——插值查找
elem[10000] = {1,2,3,4,5,6,7,8,9,……,9993,9994,9995,9996,9997,9998,9999,10000}
key = 5
int elem[10000];
for (int i = 0; i < 10000; i++)
elem[i] = i + 1;
int k = 5;
测试结果:
插值查找的比较次数相比于折半查找和斐波那契查找明显少了很多,说明在有序表的数据元素个数较大且分布较为均匀时用插值查找较为合适。
2、有序表数据个数n较大,且分布极端、不均匀——斐波那契查找/折半查找
elem[1500] = {1,2,3,4,5,6,7,8,9,……,2000,2001,2002,……,100496,199487,100498,100499};
key = 98943
for (int i = 0; i < 10; i++)
elem[i] = i + 1;
for (int i = 10; i < 100; i++)
elem[i] = i + 1990;
for (int i = 100; i < 1500; i++)
elem[i] = i + 98999;
int k = 98943;
测试结果:
在1中数据较大且均匀的有序表用插值查找比较次数较少,但面对像2中的较为极端且不均匀的数据来说,插值算法的比较次数相比于另外两个查找方法就显得逊色很多。
可以看到在这种数据的情况下,折半查找和斐波那契查找的比较次数相差不大,与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小。
若只考虑比较次数这一个因素,斐波那契查找和插值查找都较为合适,但要考虑时间因素,则更适合用斐波那契查找。
3、有序表数据个数n较小——折半查找
elem[] = { 1,2, 3 ,4,5,6,7,8,9,10,11,12,13,14,15, 23,34,39,43,46,48,50,56,60,67,68,71,75,83, 86,2000, 2003, 2013, 2040, 2043,2046,2049,2050, 2052,2054,2057,2059,2070 }
key = 23
int elem[] = { 1,2, 3 ,4,5,6,7,8,9,10,11,12,13,14,15, 23, 34, 39,43,46,48,50,56,60,67, 68, 71,75,83, 86,2000, 2003, 2013, 2040, 2043,2046,2049,2050, 2052,2054,2057,2059,2070 };
int k = 23;
测试结果:
当数据集较小时,用折半查找较为合适。
完整代码可看资源区
创作不易~麻烦点个赞~~谢谢大家~~~