【数据结构】七⼤查算法(附C语⾔代码实现)
阅读⽬录
1、顺序查
2、⼆分查
3、插值查
4、斐波那契查
5、树表查
6、分块查
7、哈希查
查是在⼤量的信息中寻⼀个特定的信息元素,在计算机应⽤中,查是常⽤的基本运算,例如编译程序中符号表的查。本⽂简单概括性的介绍了常见的七种查算法,说是七种,其实⼆分查、插值查以及斐波那契查都可以归为⼀类——插值查。插值查和斐波那契查是在⼆分查的基础上
的优化查算法。树表查和哈希查会在后续的博⽂中进⾏详细介绍。
查定义:根据给定的某个值,在查表中确定⼀个其关键字等于给定值的数据元素(或记录)。
查算法分类:
1)静态查和动态查;
注:静态或者动态都是针对查表⽽⾔的。动态表指查表中有删除和插⼊操作的表。
2)⽆序查和有序查。
⽆序查:被查数列有序⽆序均可;
有序查:被查数列必须为有序数列。
平均查长度(Average Search Length,ASL):需和指定key进⾏⽐较的关键字的个数的期望值,称为查算法在查成功时的平均查长度。
对于含有n个数据元素的查表,查成功的平均查长度为:ASL = Pi*Ci的和。
Pi:查表中第i个数据元素的概率。
Ci:到第i个数据元素时已经⽐较过的次数。
1、顺序查
说明:顺序查适合于存储结构为顺序存储或链接存储的线性表。
基本思想:顺序查也称为线形查,属于⽆序查算法。从数据结构线形表的⼀端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相⽐较,若相等则表⽰查成功;若扫描结束仍没有到关键字等于k的结点,表⽰查失败。
复杂度分析:
查成功时的平均查长度为:(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
当查不成功时,需要n+1次⽐较,时间复杂度为O(n);
所以,顺序查的时间复杂度为O(n)。
//顺序查C语⾔实现
/
/基本思路:⽤顺序结构存储数据(数组、链表),从前到后依次查询⽬标值,
//如果发现则返回查到的值,否则返回0.
#include<stdio.h>
int FindBySeq(int*ListSeq,int ListLength,int KeyData);
int main()
{
int TestData[5]={34,35,26,89,56};
int retData =FindBySeq(TestData,5,89);
printf("retData: %d\n", retData);
return0;
}
int FindBySeq(int*ListSeq,int ListLength,int KeyData)
{
int tmp =0;
int length = ListLength;
for(int i =0; i < ListLength; i++)
{
if(ListSeq[i]== KeyData)
c语言斐波那契数列return i;
}
return0;
}
2、⼆分查
说明:元素必须是有序的,如果是⽆序的则要先进⾏排序操作。
基本思想:也称为是折半查,属于有序查算法。⽤给定值k先与中间结点的关键字⽐较,中间结点把线形表分成两个⼦表,若相等则查成功;若不相等,再根据k与该中间结点关键字的⽐较结果确定下⼀步查哪个⼦表,这样递归进⾏,直到查到或查结束发现表中没有这样的结点。
***复杂度分析:***最坏情况下,关键词⽐较次数为log2(n+1),且期望时间复杂度为O(log2n);
注:折半查的前提条件是需要有序表顺序存储,对于静态查表,⼀次排序后不再变化,折半查能得到不错的效率。但对于需要频繁执⾏插⼊或删除操作的数据集来说,维护有序的排序会带来不⼩的⼯作量,那就不建议使⽤。——《⼤话数据结构》
#include<stdio.h>
//⼆分查-C语⾔实现
//基本思路:将排序好的数据存放到数组⾥(不能是链表)
//        这只前中后标签,与中间元素⽐,若⼩于就将后变为原来的中
//        继续计算中,⽐较,循环,直⾄等于中,或循环结束。
int binsearch(int*sortedSeq,int seqLength,int keyData);
int main()
{
int array[]={1,2,3,4,5,6,7,8,9};
int location;
int target =4;
location =binsearch(array,9, target);
printf("%d\n", location);
return0;
}
int binsearch(int*sortedSeq,int seqLength,int keyData)
{
int low =0, mid, high = seqLength -1;
while(low <= high)
{
mid =(low + high)/2;//奇数,⽆论奇偶,有个值就⾏
if(keyData < sortedSeq[mid])
{
high = mid -1;//是mid-1,因为mid已经⽐较过了
}
else if(keyData > sortedSeq[mid])
{
low = mid +1;
}
else
{
return mid;
}
}
return-1;
}
3、插值查
在介绍插值查之前,⾸先考虑⼀个新问题,为什么上述算法⼀定要是折半,⽽不是折四分之⼀或者折更多呢?
打个⽐⽅,在英⽂字典⾥⾯查“apple”,你下意识翻开字典是翻前⾯的书页还是后⾯的书页呢?如果再让你查“zoo”,你⼜怎么查?很显然,这⾥你绝对不会是从中间开始查起,⽽是有⼀定⽬的的往前或往后翻。
同样的,⽐如要在取值范围1 ~ 10000 之间 100 个元素从⼩到⼤均匀分布的数组中查5, 我们⾃然会考虑从数组下标较⼩的开始查。
经过以上分析,折半查这种查⽅式,不是⾃适应的(也就是说是傻⽠式的)。⼆分查中查点计算如下:mid=(low+high)/2, 即mid=low+1/2*(high-low);
通过类⽐,我们可以将查的点改进为如下:
mid=low+(key-a[low])/(a[high]-a[low])*(high-low),
也就是将上述的⽐例参数1/2改进为⾃适应的,根据关键字在整个有序表中所处的位置,让mid值的变化更靠近关键字key,这样也就间接地减少了⽐较次数。
基本思想:基于⼆分查算法,将查点的选择改进为⾃适应选择,可以提⾼查效率。当然,差值查也属于有序查。
注:对于表长较⼤,⽽关键字分布⼜⽐较均匀的查表来说,插值查算法的平均性能⽐折半查要好的多。反之,数组中如果分布⾮常不均匀,那么插值查未必是很合适的选择。
复杂度分析:查成功或者失败的时间复杂度均为O(log2(log2n))。
#include<stdio.h>
//插值查-C语⾔实现
//基本思路:⼆分查改进版,只需改⼀⾏代码。
//        mid=low+(key-a[low])/(a[high]-a[low])*(high-low)
int insertSearch(int*sortedSeq,int seqLength,int keyData);
int main()
{
int array[]={1,2,3,4,5,6,7,8,9};
int location;
int target =4;
location =insertSearch(array,9, target);
printf("%d\n", location);
return0;
}
int insertSearch(int*sortedSeq,int seqLength,int keyData)
{
int low =0, mid, high = seqLength -1;
while(low <= high)
{
mid = low +(keyData - sortedSeq[low])/(sortedSeq[high]- sortedSeq[low]);
if(keyData < sortedSeq[mid])
{
high = mid -1;//是mid-1,因为mid已经⽐较过了
}
else if(keyData > sortedSeq[mid])
{
low = mid +1;
}
else
{
return mid;
}
}
return-1;
}
4、斐波那契查
在介绍斐波那契查算法之前,我们先介绍⼀下很它紧密相连并且⼤家都熟知的⼀个概念——黄⾦分割。
黄⾦⽐例⼜称黄⾦分割,是指事物各部分间⼀定的数学⽐例关系,即将整体⼀分为⼆,较⼤部分与较⼩部分之⽐等于整体与较⼤部分之⽐,其⽐值约为1:0.618或1.618:1。
0.618被公认为最具有审美意义的⽐例数字,这个数值的作⽤不仅仅体现在诸如绘画、雕塑、⾳乐、建
筑等艺术领域,⽽且在管理、⼯程设计等⽅⾯也有着不可忽视的作⽤。因此被称为黄⾦分割。
⼤家记不记得斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…….(从第三个数开始,后边每⼀个数都是前两个数的和)。然后我们会发现,随着斐波那契数列的递增,前后两个数的⽐值会越来越接近0.618,利⽤这个特性,我们就可以将黄⾦⽐例运⽤到查技术中。
基本思想: 也是⼆分查的⼀种提升算法,通过运⽤黄⾦⽐例的概念在数列中选择查点进⾏查,提⾼查效率。同样地,斐波那契查也属于⼀种有序查算法。
相对于折半查,⼀般将待⽐较的key值与第mid=(low+high)/2位置的元素⽐较,⽐较结果分三种情况:
1)相等,mid位置的元素即为所求
2)>,low=mid+1;
3)<,high=mid-1。
斐波那契查与折半查很相似,他是根据斐波那契序列的特点对有序表进⾏分割的。他要求开始表中记录的个数为某个斐波那契数⼩1,及n=F(k)-1;
开始将k值与第F(k-1)位置的记录进⾏⽐较(及mid=low+F(k-1)-1),⽐较结果也分为三种
1)相等,mid位置的元素即为所求
2)>,low=mid+1,k-=2;
说明:low=mid+1说明待查的元素在[mid+1,high]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1))= Fk-1-F(k-
1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应⽤斐波那契查。
3)<,high=mid-1,k-=1。
说明:low=mid+1说明待查的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1个,所以可以递归 的应⽤斐波那契查。
复杂度分析:最坏情况下,时间复杂度为O(log2n),且其期望复杂度也为O(log2n)。
⽹上都没有讲清楚,导致外⾏很难直接直观的理解斐波那契查。
我认为,斐波那契的核⼼在于两点:
1. 斐波那契是⼀种特殊的分割⽅法,和⼆分、插值本质上是⼀样的,都是分割⽅法;
2. 利⽤了斐波那契数列特殊的性质,⼀个长度只要可以被黄⾦分割,那么分割后的⽚段仍然可以继续进⾏黄⾦分割,循环。
⾸先,我们构造⼀个完整的斐波那契数列,然后开始分,⼩于就取左边的分割,F变为F-1;⼤于就取右边的分割,F变为F-2。
很好理解,F-1 和 F-2,⾃⼰画个图就理解了,这是菲波那切数列特殊的性质。
#include<stdio.h>
#include<stdlib.h>
#define MAXN 20
/*
*产⽣斐波那契数列
* */
void Fibonacci(int*f)
{
int i;
f[0]=1;
f[1]=1;
for(i =2; i < MAXN;++i)
f[i]= f[i -2]+ f[i -1];
}
/*
* 查
* */
int Fibonacci_Search(int*a,int key,int n)
{
int i, low =0, high = n -1;
int mid =0;
int k =0;
int F[MAXN];
Fibonacci(F);
while(n > F[k]-1)//计算出n在斐波那契中的数列
++k;
for(i = n; i < F[k]-1;++i)//把数组补全
a[i]= a[high];
while(low <= high)
{
mid = low + F[k -1]-1;//根据斐波那契数列进⾏黄⾦分割
if(a[mid]> key)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。