一、查找的基本概念
1、基本概念
查找表(Search Table):由相同数据类型的数据元素构成的集合(线性表、树、散列表)。
关键字(key):数据元素中某个数据项,它可以标识一个数据元素,是唯一的。
查找(Searching):根据给定的某个值,在查找表中找到一个关键字等于给定值的数据元素。如果表中存在这样的一个数据元素,则称查找是成功的,否则查找是失败的。
静态查找表(Static Search Table):查找表中的数据元索不会发生变化。
动态查找表(Dynamic Search Table):查找表中的数据元素会发生变化(插入、修改和删除)。
查找长度(Search Length):在查找运算中,需要对比关键字的次数。
平均查找长度(ASL,Average Serach Length):所有查找过程中进行关键字比较次数的平均值,用于衡量查找算法的效率,包括成功和失败两种情况。
二、查找算法
1、顺序查找
顺序查找(线性查找):用于在静态的线性表(顺序表或链表)中进行查找。算法思想是从线性表的某一端开始,把表中的元素与关键字逐个比较(线性表的遍历)。
例子:
代码实现:
/*
* 程序名:seqsearch.c,此程序演示顺序查找。
* 作者:C语言技术网(www.freecplus.net) 日期:20210325
*/
#include <stdio.h>
#include <string.h>
// 对无序的查找表进行顺序查找。
// 在sstable中查找key,失败返回-1,成功返回key在sstable中的数组下标。
int Seq_Search1(int *sstable,unsigned int len,int key)
{
int ii;
for (ii=0;ii<len;ii++) // 从前往后或从后往前找都行。
if (sstable[ii]==key) break; // 找到了就break
if (ii==len) return -1; // 查找失败时,ii==len
return ii;
}
/*
// 对无序的查找表进行顺序查找更精简的写法。
// 在sstable中查找key,失败返回-1,成功返回key在sstable中的数组下标。
int Seq_Search1(int *sstable,unsigned int len,int key)
{
int ii;
for (ii=0;ii<len&&sstable[ii]!=key;ii++) // 从前往后或从后往前找都行。
;
return ii==len?-1:ii;
}
*/
// 设置哨兵对无序的查找表进行顺序查找。
// 在sstable中查找key,失败返回0,成功返回key在sstable中的数组下标。
int Seq_Search2(int *sstable,unsigned int len,int key)
{
int ii;
sstable[0]=key; // 哨兵。
for (ii=len-1;sstable[ii]!=key;ii--) // 从后往前找。
; // 注意这个空语句。
return ii; // 找不到时,ii为0
}
// 对有序的查找表进行顺序查找。
// 在sstable中查找key,失败返回-1,成功返回key在sstable中的数组下标。
int Seq_Search3(int *sstable,unsigned int len,int key)
{
int ii;
for (ii=0;ii<len;ii++) // 从前往后或从后往前找都行。
{
if (sstable[ii]==key) break; // 找到了就break
if (sstable[ii] >key) return -1; // 不必再找了,返回-1
}
if (ii==len) return -1; // 查找失败时,ii==len
return ii;
}
// 采用跳跃的方法对有序的查找表进行顺序查找。
// 在sstable中查找key,失败返回-1,成功返回key在sstable中的数组下标。
int Seq_Search4(int *sstable,unsigned int len,int key)
{
int ii=0;
while (ii<len && sstable[ii]<key) ii=ii+3; // 每次跳跃三个元素。
if (ii<len && sstable[ii]==key) return ii; // 查找成功。
else if (ii-1<len && sstable[ii-1]==key) return ii-1; // 查找成功。
else if (ii-2<len && sstable[ii-2]==key) return ii-2; // 查找成功。
return -1; // 查找失败。
}
int main()
{
// 对无序的查找表进行顺序查找。
int sstable1[]={2,5,6,3,1,7,4,8,9};
int len=sizeof(sstable1)/sizeof(int);
printf("result1=%d\n",Seq_Search1(sstable1,len,7));
printf("result1=%d\n",Seq_Search1(sstable1,len,15));
printf("result1=%d\n",Seq_Search1(sstable1,len,2));
printf("result1=%d\n",Seq_Search1(sstable1,len,9));
printf("\n");
// 设置哨兵对无序的查找表进行顺序查找。
int sstable2[]={0,2,5,6,3,1,7,4,8,9};
len=sizeof(sstable2)/sizeof(int);
printf("result2=%d\n",Seq_Search2(sstable2,len,7));
printf("result2=%d\n",Seq_Search2(sstable2,len,15));
printf("result2=%d\n",Seq_Search2(sstable2,len,2));
printf("result2=%d\n",Seq_Search2(sstable2,len,9));
printf("\n");
// 对有序的查找表进行顺序查找。
int sstable3[]={1,2,3,4,5,6,7,8,9};
len=sizeof(sstable3)/sizeof(int);
printf("result3=%d\n",Seq_Search3(sstable3,len,7));
printf("result3=%d\n",Seq_Search3(sstable3,len,15));
printf("result3=%d\n",Seq_Search3(sstable3,len,1));
printf("result3=%d\n",Seq_Search3(sstable3,len,9));
printf("\n");
// 采用跳跃的方法对有序的查找表进行顺序查找。
int sstable4[]={1,2,3,4,5,6,7,8,9};
len=sizeof(sstable4)/sizeof(int);
printf("result4=%d\n",Seq_Search4(sstable4,len,0));
printf("result4=%d\n",Seq_Search4(sstable4,len,7));
printf("result4=%d\n",Seq_Search4(sstable4,len,15));
printf("result4=%d\n",Seq_Search4(sstable4,len,1));
printf("result4=%d\n",Seq_Search4(sstable4,len,9));
printf("\n");
return 0;
}
顺序查找的优化方案
改善前:
// 对无序的查找表进行顺序查找。
// 在sstable中查找key,失败返回-1,成功返回key在sstable中的数组下标。
int Seq_Search1(int *sstable,unsigned int len,int key)
{
int ii;
for (ii=0;ii<len;ii++) // 从前往后或从后往前找都行。
if (sstable[ii]==key) break; // 找到了就break
if (ii==len) return -1; // 查找失败时,ii==len
return ii;
}
1、设置哨兵方案
假设我需要在下图表中查找元素7;我们可以把7放在数组下标为0的位置,然后从数组的后面开始逐个元素和7比较;
改善后:
// 设置哨兵对无序的查找表进行顺序查找。
// 在sstable中查找key,失败返回0,成功返回key在sstable中的数组下标。
int Seq_Search2(int *sstable,unsigned int len,int key)
{
int ii;
sstable[0]=key; // 哨兵。
for (ii=len-1;sstable[ii]!=key;ii--) // 从后往前找。
; // 注意这个空语句。
return ii; // 找不到时,ii为0
}
由上面两段代码可知,循环的条件从判断 ii<len 变成了 sstable[ii]!=key,改善前的循环体还需要做两次判断,而改善后不需要,因而提高了效率;
2、先排序
假设我需要在下图有序表中查找元素23,可找到;
查找24时,查找到23,发现23<24,指针向后移动,发现25>24,此时就不会向后寻找了,因为数组时有序的,后面的数都比25大;
改善后:
// 对有序的查找表进行顺序查找。
// 在sstable中查找key,失败返回-1,成功返回key在sstable中的数组下标。
int Seq_Search3(int *sstable,unsigned int len,int key)
{
int ii;
for (ii=0;ii<len;ii++) // 从前往后或从后往前找都行。
{
if (sstable[ii]==key) break; // 找到了就break
if (sstable[ii] >key) return -1; // 不必再找了,返回-1
}
if (ii==len) return -1; // 查找失败时,ii==len
return ii;
}
这种情况对于成功的查找不会有什么改进,但是对于不存在的元素有比较大的效率改进;
3、跳跃
假设依旧是在下面有序表中,查找元素23,跳跃法,就是如果比较元素不一致,指针就向后跳动几个位置;
改善后:
// 采用跳跃的方法对有序的查找表进行顺序查找。
// 在sstable中查找key,失败返回-1,成功返回key在sstable中的数组下标。
int Seq_Search4(int *sstable,unsigned int len,int key)
{
int ii=0;
//每次跳跃3个元素,自定,也可以跳跃少或者多几个
while (ii<len && sstable[ii]<key) ii=ii+3; // 每次跳跃三个元素。
if (ii<len && sstable[ii]==key) return ii; // 查找成功。
else if (ii-1<len && sstable[ii-1]==key) return ii-1; // 查找成功。
else if (ii-2<len && sstable[ii-2]==key) return ii-2; // 查找成功。
return -1; // 查找失败。
}v
效率分析
无序查找表:
第一个元素需要比较1次,第二个2次,第3个3次,第n个n次;
ASL成功= (1+2+3+…n)/n=n(n+1)/2n=(n+1)/2
失败的话从表头到表尾,比较n次;
ASL失败=n;