三种查找算法比较:折半查找(二分查找)、插值查找、斐波那契查找

1.三种查找算法思想及实现

1.1插值查找

1.算法思想
插值查找类似于折半查找,不同的是插值查找每次从自适应mid处开始查找。求mid的索引公式如下:
mid=low+(key-arr[low])*((high-low)/(arr[high]-arr[low]))
该公式中,要查找的元素为key,数组的下界为low,上界为high,arr表示待查找的有序数组。该公式中,(key-arr[low])表示要查找元素与数组第一个元素的差值,(high-low)表示数组范围,(arr[high]-arr[low])表示数组最后一个元素与第一个元素的差值。这个公式类似于线性插值公式,根据待查找元素的位置进行估算。
2.算法流程
插值查找算法的流程与折半查找类似,不同之处在于插值查找算法使用插值公式来计算查找的位置算法代码实现。
3.算法代码实现:

//插值查找递归实现 
int CZ_search(int *a, int value, int low, int high)
{
	if (low > high)
		return -1;
	int low1 = low, high1 = high;
	int mid = low1 + (high1 - low1)*((value - a[low1]) / (a[high1] - a[low1]));
	//查找点更新公式 
	if (a[mid] < value)
		return CZ_search(a, value, mid+1 , high1);
	else if (value < a[mid])
		return CZ_search(a, value, low1, mid-1);
	else if (a[mid] == value)
		return mid;
}
//插值查找非递归实现                                
int CZsearch(int *a, int value, int low, int high)
{
	while (low < high)
	{
		int mid = low + (high - low)*((value - a[low]) / (a[high] - a[low]));
		//查找点更新公式 
		if (value < a[mid])
			high = mid - 1;
		else if (value > a[mid])
			low = mid + 1;
		else
			return mid;
	}
	return -1;
}

4.时间复杂度
插值查找算法的时间复杂度为O(log(log n)),这是由于插值查找算法的查找位置是根据元素的估计位置来计算的,因此每次查找的区间不一定是二分的,所以查找效率比折半查找更高,尤其是在数据分布均匀的情况下。

1.2斐波那契查找

(1)基本思想
斐波那契搜索(Fibonacci search) ,又称斐波那契查找,是区间中单峰函数的搜索技术。斐波那契查找就是在二分查找的基础上根据斐波那契数列进行分割的。在斐波那契数列找一个等于或第一个大于查找表中元素个数的数 F[n],然后将原查找表的长度扩展为 Fn (如果要补充元素,则重复补充原查找表最后一个元素,直到原查找表中满足 F[n] 个元素);扩展完成后进行斐波那契分割,即 F[n] 个元素分割为前半部分 F[n-1] 个元素,后半部分 F[n-2] 个元素;接着找出要查找的元素在哪一部分并递归,直到找到。其查找点的计算公式如下:
mid=left+F(n-1)-1
(2)算法流程
①构建斐波那契数列;
②找出查找表长度对应的斐波那契数列中的元素 F(n);
③如果查找表长度小于斐波那契数列中对应的元素 F(n) 的值,则补充查找表(以查找表最后一个元素补充);
④根据斐波那契数列特点对查找表进行区间分隔,确定查找点 mid = left+F(n-1)-1(减 1 是因为数组下标从 0 开始;
⑤判断中间值 arr[mid] 和目标值的关系,确定下一步策略:
如果目标值小于中间值,说明目标值在左区间。由于左区间长度为 F(n-1)-1,因此 n 应该更新为 n-1,然后再次执行 4、5 两步;
如果目标值大于中间值,说明目标值在右区间。由于右区间长度为 F(n-2)-1,因此 n 应该更新为 n-2,然后再次执行 4、5 两步;
如果目标值等于中间值,说明找到了目标值。但此时还需判别该目标值是原查找表中的元素还是填充元素:如果是原查找表中的元素,直接返回索引;如果是填充元素,则返回原查找表的最后一个元素的索引,即arr.length-1。(因为扩展数组是以原查找表最后一个元素来填充,如果目标值是填充元素,则说明原查找表最后一个元素值就是目标值)。
(3)算法代码实现:

// 斐波那契查找算法
// a为目标查找数组、n为数组长度、 value为查找目标值、算法中的F为斐波那契数组 
int Fib_search(int *a, int n, int value) {     
    int k = 0;
    while (n > F[k]) {					
        k ++;
    }
    for (int i = n; i < F[k] - 1; ++i) {
        a[i + 1] = a[i];
    }
    int low = 1;
    int high = n;
    while (low <= high) {
        int mid = low + F[k - 1] - 1;
        //查找点的计算公式
        if (value == a[mid]) {
            if(mid<=high)
                return mid;
            else
                return high;
            //如果找到了目标值。此时还需判别该目标值是原查找表中的元素还是填充元素    
        } else if (value > a[mid]) {
            low = mid + 1;
            k -= 2;
            //待查找的元素在[mid+1,high]范围内,[mid+1,high]内的元素个数为F(k-2)-1个,即将k更新为k-2,进入下次循环  
        } else if (value < a[mid]) {
            high = mid - 1;
            k--;
            //待查找的元素在[low,mid-1]范围内,[low,mid-1]内的元素个数为F(k-1)-1个,即将k更新为k-1,进入下次循环 
        }
    }
    return -1; 							
}

(4)时间复杂度
斐波那契查找法的时间复杂度为O(log n),这是由于斐波那契数列的增长速度比指数函数慢,因此扩展数组长度后,每次查找可以将查找区间缩小至F(k-1)-1或F(k-2)-1个元素,从而达到快速查找的效果。

1.3折半查找

(1)基本思想
折半查找也称为二分查找(Binary search),要求线性表中的节点必须己按关键字值的递增或递减顺序排列。其基本思想是:首先用要查找的关键字k与中间位置的结点的关键字相比较,这个中间结点把线性表分成了两个子表,若比较结果相等则查找完成;若不相等,再根据k与该中间结点关键字的比较大小确定下一步查找哪个子表,这样递归进行下去,直到找到满足条件的结点或者该线性表中没有这样的结点。
(2)算法流程
①在有序序列中查找元素,每次取序列中间的元素,如果与要查找元素相等,程序结束;
②如果查找元素大于中间元素,则取中间元素后面的序列再进行如上的查找;
③如果查找元素小于中间元素,则取中间元素前面的序列再进行如上的查找;
④直到找到元素相等,查找成功,或者序列为空,查找失败,程序结束。
(3)算法代码实现
此代码在本文章暂时不做过多介绍,对于有打算法竞赛的同学,这里推荐一个up主讲解二分查找算法的视频,本人是看此up主算法视频课才慢慢开始入门的。放上视频链接二分查找算法视频讲解
(4)时间复杂度
折半查找算法中每次比较后,查找范围就会缩小一半,直到查找到目标元素或者查找范围缩小到0为止。因此,折半查找的时间复杂度为O(log n)。在有序数组中使用折半查找可以高效地查找目标元素。
最优情况:如果待查找的元素恰好是中间位置的元素,则折半查找的时间复杂度为O(1)。这是最优的情况,因为只需要一次比较即可找到目标元素。
最坏情况:如果待查找的元素不在数组中,则折半查找的时间复杂度为O(log n)。这是最坏的情况,因为需要进行O(log n)次比较才能确定目标元素不在数组中。
平均情况:对于长度为n的有序数组,折半查找的平均时间复杂度为O(logn)。平均情况下,需要进行O(logn)次比较才能找到目标元素或者确定目标元素不在数组中。

2.三种查找算法的比较实验结果分析

2.1数据量较小时的比较

在本对比实验中,设置了数据量大小为100的有序数组,代码如下:

 int arr[100];
    int n = 100;
    for (int i = 0; i < n; i++) {
        arr[i] = i;
    }

​ 在0-100之间随机生成要查找的数字进行查找测试,我使用了clock()函数来计算程序运行时间。在每个算法中,我都记录了比较次数和赋值次数,并使用它们来计算算法的空间复杂度。所有三种算法的空间复杂度都是O(1),因为它们都只使用了几个额外的变量来保存索引和比较/赋值计数器。这里的测试代码以折半查找测试代码为例(如下),其余查找算法的测试代码均与此相似。

 // 折半查找测试
    int comp = 0, assig = 0, found = 0;
    clock_t start = clock(), end;
    for (int i = 0; i < n; i++) {
        int x = rand() % n;
        int index = binarySearch(arr, 0, n - 1, x, comp, assig);
        if (index != -1) {
            found++;
        }
    }
    end = clock();
    double timeTaken = ((double)(end - start)) / CLOCKS_PER_SEC;

三种查找算法的最终结果如下: 在这里插入图片描述
有序数据集(小)下三种查找算法的比较结果

实验结果分析
1.比较次数
折半查找的比较次数最多,而插值查找的比较次数最少,这是因为折半查找每次都取区间的中间位置进行比较,而插值查找则根据要查找的元素的大小,来动态地计算出可能存在的位置,因此可以更快地找到要查找的元素。
斐波那契查找的比较次数相对折半查找略少,但比插值查找略多,这是因为斐波那契查找在分割区间时采用黄金分割点,可以比折半查找更快地逼近要查找的元素。
2.赋值次数
赋值次数反映了算法在运行过程中临时变量的使用情况。这些算法都是比较简单的算法,不涉及过多的临时变量,因此赋值次数也都比较少。
插值查找的赋值次数最少,因为它的算法中只需要使用一个变量来记录计算出的位置。
斐波那契查找的赋值次数相对较多,因为在斐波那契查找中需要使用多个变量来计算黄金分割点和新的区间范围。
3.查找成功率
三种算法的查找成功率都相同,这说明在这个顺序数据集中,这三种算法都能够正确地找到要查找的元素。
4.平均查找次数
插值查找的平均查找次数最少,而折半查找和斐波那契查找的平均查找次数分别为5.96和5.93。这是因为插值查找能够根据数据的分布情况,更快地逼近目标值,从而能够以更少的比较次数找到目标值。而折半查找和斐波那契查找的逼近方式相对固定,因此需要更多的比较次数才能找到目标值
5.平均查找时间
由于测试数据集比较小,所以三种算法的平均查找时间都为0秒。如果测试数据集比较大,就能够更准确地反映三种算法的查找效率。
6.空间复杂度
三种算法的空间复杂度都是O(1),这也符合预期,因为这些算法都不需要使用额外的空间来存储数据结构。

2.2数据量较大时的比较

增大数据量,将数组大小调整为100,000,测试结果如下:
在这里插入图片描述

有序数据集(大)下三种查找算法的比较结果

实验结果分析
1.比较次数:
折半查找需要对每次查找的区间进行一次比较,因此随着数据量的增大,比较次数也会随之增多,最终达到了 1568406 次。插值查找和斐波那契查找的比较次数分别为 200000 和 1581939 次,插值查找相较于折半查找,由于可以根据要查找的元素的大小动态地计算出可能存在的位置,因此比较次数相对较少;而斐波那契查找在分割区间时采用黄金分割点,可以比折半查找更快地逼近要查找的元素,因此比较次数相对较少。
2.赋值次数:
赋值次数反映了算法在运行过程中临时变量的使用情况。在这个测试中,插值查找的赋值次数最少,只有 100000 次,因为它的算法中只需要使用一个变量来记录计算出的位置;折半查找和斐波那契查找的赋值次数分别为 1568406 和 3063878 次,这是因为折半查找和斐波那契查找在分割区间时需要使用多个变量来计算新的区间范围或黄金分割点。
3.查找成功率和平均查找次数:
三种算法的查找成功率都是 100%。平均查找次数不同,折半查找的平均查找次数最高,为 15.6841,而插值查找的平均查找次数最低,为2,斐波那契查找的平均查找次数为 15.8194。这是因为插值查找算法根据数据的分布情况动态地调整查找区间,能够更快地找到目标元素,而折半查找和斐波那契查找则采用固定的分割方式,因此平均查找次数相对较高。
4.平均查找时间:
三种算法的平均查找时间都很短。测试数据集较小,因此三种算法的运行时间不会受到较大的影响,但如果数据集较大,插值查找会更快一些。
5.空间复杂度:三种算法的空间复杂度都是 O(1)。

3.结论

三种查找算法都是基于在有序数据集的情况下进行查找,折半查找和斐波那契查找算法的时间复杂度都为O(logn),插值查找算法的时间复杂度为O(log(log n))。其中插值查找在分布均匀的有序数据集上展现出了非常明显的优势。

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值