1. 线性表的查找
线性表的查找主要有:顺序查找,二分查找,分块查找。
通过前面的学习可知,线性表有顺序和链式两种存储结构,而这里我们主要是介绍的线性表查找算法是基于顺序存储结构的,其顺序表的存储类型定义:
#define MAXL 100
typedef int KeyType;
typedef char InfoType[10];
typedef struct
{
KeyType key; //KeyType为关键字的数据类型
InfoType data; //其他数据项
} NodeType;
typedef NodeType SeqList[MAXL]; //查找顺序表类型
2. 顺序查找
例如,查找表:3 9 1 5 8 10 6 7 2 4,要查找的值:k=10
图1-顺序查找
顺序查找思路如图1所示:
从表的一端开始,顺序扫描线性表,依次将扫描到的关键字和给定值k相比较。若当前扫描到的关键字与k相等,则查找成功;若扫描结束后,仍未找到关键字等于k的记录,则查找失败。
顺序查找:
/*
顺序查找:
参数R表示要查找的顺序表
n表示元素个数
k表示要查找的元素
*/
int SeqSearch(SeqList R , int n , KeyType key)
{
int i;
for(i = 0; i < n; i++)
{
if(R[i].key == key)
{
//找到了返回逻辑序号i+1
return i+1;
}
}
return -1;
}
3. 折半查找
折半查找也称为二分查找,要求线性表中的节点,按关键字值的递增或递减顺序排列。
图2-折半查找
折半查找的思路:
1. 首先用要查找的关键字k与中间位置的节点的关键字比较,若比较结果相等则查找完成。若不相等,再根据k与该中间节点关键字的比较大小确定下一步查找哪个子表。
2 . 递归进行下去,直到找到满足条件的节点或者该线性表中没有这样的节点。
折半查找算法实现:
/*
参数R表示要查找的顺序表
参数n表示元素个数
参数k表示要查找的元素
*/
int BinSearch(SeqList R,int n,KeyType k)
{
int low=0,high=n-1,mid;
//当前区间存在元素时循环
while (low<=high)
{
mid=(low+high)/2;
//查找成功返回其逻辑序号mid + 1
if (R[mid].key==k)
{
return mid+1;
}
//否则继续在[high ... mid - 1]中查找
if (R[mid].key>k)
{
high=mid-1;
}
//继续在[low ... mid - 1]中查找
else
{
low=mid+1;
}
}
//查找失败则返回0
return 0;
}
二分查找算法递归版实现:
/*
二分查找的递归版算法
low表示开始下标
high表示结尾下标
*/
int BinSearch1(SeqList R,int low,int high,KeyType k)
{
int mid;
if (low<=high)
{
mid=(low+high)/2;
if (R[mid].key==k)
{
return mid+1;
}
if (R[mid].key>k)
{
BinSearch1(R,low,mid-1,k);
}
else
{
BinSearch1(R,mid+1,high,k);
}
}
return 0 ;
}
二分查找的特点:利用了顺序查找的有序性,使得查找过程得到提高。
4. 索引存储结构
图3-数据表
上图中有一个城市的数据表,其中区号是作为数据表中的关键字,数据表中的区号是无序的,当我们要从这张数据表中去查找一个城市的数据,应该怎么去查找呢?
根据我们前面学过的查找算法可知,有两种:顺序查找和折半查找。
对于顺序查找来说,当数据表中的数据量非常大的时候,那么顺序查找需要一个一个的去查找数据,直到查找到数据为止,显然顺序查找的效率是比较低。对于折半查找来说,也存在同样的问题,因为折半查找的数据前提是有序的,而当数据表中的数据量非常大的时候,对数据进行排序非常耗时,因此折半查找的效率也是非常低的。
图4-索引存储结构
当数据量非常大的时候,对于这种情况,在实际的应用中更多的是采用索引存储结构来实现,而索引存储结构一般是由索引表和数据表组成的。
索引表中的每一项都成为索引项,索引项中又分为两部分:关键字和地址,关键字用于唯一标识一个节点, 比如:索引表中的025关键字就表示了在数据表中的一个城市的相关数据(区号,城市名,说明)。
地址作为指向该关键字对应节点的指针,也可以是相应的地址。比如在索引表中,我们可以根据025关键字所在的索引项中找到地位为210的,然后通过210地址直接在数据表中找到相关城市数据。
我们可以知道索引存储结构,这种解决方案是通过索引表来查找数据的,也就是说,当我们在索引表中查找到数据的索引项时,直接通过索引项中存储的地址,就可以从数据表中访问到对应的城市数据。特别是针对数据量非常大的时候,避免了直接查找数据表,从而提高查找效率。
5. 分块查找
下面通过一个例子来说明分块查找
假设有一个线性表,其中包含23个记录:
关键字序列:8 , 14 , 6 , 9 , 10 , 22 , 34 , 18 , 19 , 31 , 40 , 38 , 54 , 66 , 46 , 71 , 78 , 68 , 80 , 85 , 99 , 94 , 88
我们要做的就是将23个记录分为5块,每块中有5个记录,在进行分块时,需要满足以下几个条件:
1. 将数据表R中23个记录均分为b块,前b-1块中,每块的记录个数为5,但是最后一块即第b块的记录数小于等于5;
2 . 每一块中的关键字不一定有序,但前一块中的最大关键字必须小于后一块中的最小关键字,即要求表是“分块有序”的。
3 . 抽取各块中的最大关键字,构建索引表Idx, 在索引表Idx中的link存放着第i块在表R中的起始位置,而索引表Idx中的key存放着每一块中的最大关键字,由于表R是分块有序的,所以索引表是一个递增有序表。
最后构建成的分块索引表如下所示:
图5-索引存储结构
当我们在这样的分块索引表中查找就可以这样做:
1 . 先在索引表中查找关键字所在的块,由于索引表中的块是有序的,因此可以利用折半查询,也可以利用顺序查询
,比如查找关键字为80的,即 66< 80 < 85,在第4个分块中。
2 . 于是再根据分块中的link找到分块中的关键字起始位置,由于分块内的关键字是无序的,因此只能通过顺序查找
。比如找到第4块的关键字起始位置(71),通过顺序查找逐个比较,最终在第四次查找中找到了关键字为80的。
由此可知,在索引表中顺序查找的话,需要查找4次,在对应的块中查找,需要查找4次,加起来总共需要8次比较。但如果我们直接在数据表R中采用顺序查找的话,总共需要19次才能找到。也就是说,分块查找的性能是介于顺序查找和折半查找之间的
。
存储结构:
#define MAXL 100 //数据表的最大长度
#define MAXI 20 //索引表的最大长度
typedef int KeyType;
typedef char InfoType[10];
typedef struct
{
KeyType key; //关键字
InfoType data; //其他数据
} NodeType;
typedef NodeType SeqList[MAXL]; //数据表类型
SeqList R;
typedef struct
{
KeyType key; //KeyType为关键字的类型
int link; //对应块的起始下标
} IdxType;
typedef IdxType IDX[MAXI]; //索引表类型
IDX idx;
分块查找算法实现:
//m表示数组I(索引表)中的元素个数
//n表示数组R(数据表)中的元素个数
//k表示要查找的关键字
int IdxSearch(IDX I , int m , SeqList R , int n , KeyType k)
{
int low=0 , high = m-1 , mid , i;
//b表示每个分块中的个数
int b = n / m;
//在索引表中“折半”查找,主要是查找分块所在的位置
while (low <= high)
{
mid = (low+high)/2;
if (I[mid].key >= k)
high=mid-1;
else
low = mid+1;
}
//在数据表中顺序查找
i = I[high+1].link;
while (i <= I[high+1].link+b-1 && R[i].key != k)
i++;
//如果找到了则返回关键字的逻辑下标+1
if (i <= I[high+1].link+b-1)
return i+1;
//否则没找到就返回0
else
return 0;
}