1.查找概论
关键字(key):是数据元素中某个数据项的值,又称为键值。
主关键字:若此关键字可以唯一地标识一个记录,则称此关键字为主关键字。
次关键字:那些可以识别多个数据元素(或记录)的关键字,称为次关键字。
查找:就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
查找表:是由同一类型的数据元素(或记录)构成的集合。
静态查找表:只作查找操作的查找表。
- 查询某个“特定的”数据元素是否在查找表中。
- 检索某个“特定的”数据元素和各种属性。
动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。 - 查找时插入数据元素。
- 查找时删除数据元素。
2.顺序表查找算法
//顺序查找,a为数组,n为要查找的数组长度,key为要查找的关键字
int Sequential_Search(int *a,int n,int key)
{
int i;
for(i=1;i<=n;i++)
{
if(a[i]==key)
return i;
}
}
到这里并非足够完美,因为每次循环时都需要对i是否越界,即是否小于等于n作判断。事实上,还可以有更好一点的办法,设置一个哨兵,可以解决不需要每次让i与n作比较。看下面的改进后的顺序查找算法代码。
int Sequential_Search2(int *a,int n,int key)
{
int i;
a[0]=key;//设置a[0]为关键值,我们称之为“哨兵”
i=n;//循环从数组尾部开始
while(a[i]!=key)
i--;
return i;//返回则说明查找失败
}
这种在查找方向的尽头放置“哨兵”免去了在查找过程中每一次比较后都要判断查找位置是否越界的小技巧,看似与原先差别不大,但在总数据较多时,效率提高很大,是非常好的编程技巧。
3.有序表查找
很显然,顺序查找技术是有很大缺点的,n很大时,查找效率极为低下,但这个算法非常简单,对静态查找表的记录没有任何要求,在一些小型数据的查找时,是可以适用的。另外,也正由于查找概率的不同,我们完全可以将容易查找到的记录放在前面,而不常用的记录放置在后面,效率就可以大幅提高,这里介绍一些效率较高的有序查找方法。
- 折半查找:它的前提是线性表中的记录必须是关键码有序,线性表必须采用顺序存储。折半查找的基本思想:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,知道查找成功,或所有查找区域无记录,查找失败为止。下面我们来看看折半查找算法是如何实现的。
int Binary_Search(int *a,int n,int key)
{
int low,high,mid;
low=1;//定义最低下标为记录首位
high=n;//定义最高下标为记录末位
while(low<=high)
{
mid=(low+high)/2;//折半
if(key<a[mid])//若查找值比中值小
high=mid-1;//最高下标调整到中位下标小一位
else if(key>a[mid])//若查找值比中值大
low=mid+1;//最低下标调整到中位下标大一位
else
return mid;//若相等则说明mid即为查找的到的位置
}
return 0;
}
- 插值查找:插值查找是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式(key-a[low])(a[high]-a[low])。
- 斐波那契查找:
我们根据代码来看程序是如何运行的。
int Fibonacci_Search(int *a,int n,int key)
{
int low,high,mid,i,k;
low=1;//定义最低下标为记录首位
high=n;//定义最高下标为记录末位
k=0;
while(n>F[k]-1)//计算n位于斐波那契数列的位置
k++;
for(i=n;i<F[k]-1;i++){//将不满的数值补全
a[i]=a[n];
}
while(low<=high)
{
mid=low+F[k-1]-1;//计算当前分隔的下标
if(key<a[mid])//若查找记录小于当前分隔记录
{
high=mid-1;//最高下标调整到分隔下标mid-1处
k=k-1;//斐波那契数列下标减一位
}
else if(key>a[mid])//若查找记录大于当前分隔记录
{
low=mid+1;//最低下标调整到分隔下标mid+1处
k=k-2;//菲波那切数列下标减两位
}else{
if(mid<=n)
return mid;//若相等则说明mid即为查找到的位置
else
return n;//若mid>n说明是补全数值,返回n
}
}
return 0;
}
我们通过演示一个实例来具体讲解斐波那契数列的原理。如下图所示的示例,注意此时我们已经有了事先计算好的全局变量数组F的具体数据,它是斐波那契数列,F={0,1,1,2,3,5,8,13,21…}。
1.代码第6~8行是计算当前的n处于斐波那契数列数列的位置。现在n=10,F[6]<N<F[7],所以计算得出k=7。
2.代码第9~10行,由于k=7,计算时以F[7]=13为基础,而a中最大的仅是a[10],后面的a[11],a[12]均未赋值,这不能构成有序数列,因此将它们都赋值为最大的数组值,所以此时a[11]=a[12]=a[10]=99。
3.第11~31行查找正式开始。
4.第13行,mid=1+F[7-1]-1=8,也就是说,我们第一个要对比的数值是从下标为8开始的。
5.由于此时key=59而a[8]=73,因此执行第16~17行,得到high=7,k=6。
6.再次循环,mid=1+F[6-1]-1=5。此时a[5]=47<key,因此执行第21~22行,得到low=6,k=6-2=4。注意此时k下调2个单位。
7.再次循环,mid=6+F[4-1]-1=7。此时a[7]=62>key,因此执行第16~17行,得到high=6,k=4-1=3。
8.再次循环,mid=6+F[3-1]-1=6。此时a[6]=59=key,因此执行第26~27行,得到返回值为6.程序运行结束。
斐波那契数列查找算法的核心在于:
- 当key=a[mid]时,查找就成功;
- 当key<a[mid],新范围是第low个到第mid-1个,此时范围个数为F[k-1]-1个。
- 当key>a[mid],新范围是第m+1个到第high个,此时范围个数为F[k-2]-1个。
4.总结
折半查找是进行加法与除法运算,插值查找进行复杂的四则运算,而斐波那契查找只是简单加减法运算,在海量数据的查找过程中,这种细微的差别可能会影响最终的查找效率。应该说,三种有序表的查找本质是分隔点的选择不同,各有优劣,实际开发时,可以根据数据的特点综合考虑再做出选择。