一、基本概念
1、查找表:同一类型的数据元素(记录)构成的集合。
2、静态查找表:对查找表只进行查找操作。动态查找表:不仅进行查找操作,而且在查找过程中还伴随着插入(查找的数据元素不在表中时)、删除某个数据元素的操作。
3、关键字(key):是数据元素(或记录)的某个数据项的值,用它可标识(识别)一个数据元素(或记录)。
4、平均查找长度(ASL):需和给定值进行比较的关键字个数的期望。
二、常见的算法及其思想
我们的存储结构为:
typedef struct
{ ElemType *elem;
int length;
}SSTable;
1、简单顺序查找
从表的一端开始,逐个把每条记录的关键字值与给定值k进行比较。若某个记录关键字值与给定值k相等,则查找成功,返回找到的记录位置。反之,若已查找到表的另一端,仍未找到关键字值与给定值相等的记录,则查找不成功,返回-1。我们用第一个存储单元来存储要查找的数值,来免去查找过程中每一步都要检测整个表是否查找完毕。这个单元起了监视哨的作用。
2、折半查找
首先确定待查记录所在的范围(区域)。假设用变量low和high分别表示当前查找区域的首尾下标。将待查关键字k和该区域的中间元素,其下标为mid=(low+high)/2的关键字进行比较。若相等,则查找成功,返回此位置值;否则若所找记录关键字大,则取后半部记录,否则取其前半部记录。
3、分块查找(也叫索引顺序表)
首先把表长为n的线性表分成b块,前b-1块记录个数为s=n/b,第b块的记录个数小于等于s。在每一块中,结点的存放不一定有序,但块与块之间必须是分块有序的(假定按结点的关键字值递增有序)。即指后一个块中所有记录的关键字值都应比前一个块中所有记录的关键字值大。为实现分块检索,还需建立一个索引表。索引表的每个元素对应一个块,其中包括该块内最大关键字值和块中第一个记录位置的地址指针。显然这个索引顺序表是一个递增有序表。
基本思想:先建立索引(最大(最小)关键字表)。要查找关键字为key的元素,先按折半查找获得key对应记录所在的块,再按线性(顺序)查找在块中找到key对应的记录。
4、静态树表的查找
我们前面的几种方法都是假定查找是等概率然情况下进行的。实际中,如果概率是不等的,情况又如何呢?即使按折半查找中判断树进行,也不一定最优。使查找性能最佳的判定树是其带权内路径长充之和PH值
三、算法的C语言描述
1、简单顺序查找
2、折半查找
3、分块查找(也叫索引顺序表)
#define MaxIndex <索引表的最大长度>
typedef struct{
KeyType key;
int link;
}IdxType;
int IdxSerch(SSTable ST,IdxType index[],int b,KeyType key)
{ //分块查找关键字为k的记录,索引表为index[0..b-1]
int low=0,high=b-1,mid,i;
int s=ST.length/b; //每块记录个数
while(low<=high)
{ //在索引表中进行折半查找,找到的位置放在low中
mid=(low+high)/2;
if(index[mid].key<key) low=mid+1;
else high=mid-1;
}//while
if(low==b) low--;
if(low<=b) //在顺序表中顺序查找
{
for(i=index[low].link;(i<=index[low].link+s-1)&&(i<=ST.length);i++)
{
//printf(" %d %d",ST.elem[i].key,key);
if(ST.elem[i].key==key) return i;
}//for
return -1;
}//if
else return -1;
}//IdsSerch
4、静态树表的查找
构造次优查找树的递归算法如下:
按有序表构造次优二叉树的算法如:
四、算法的C语言实现
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "math.h"
#define OK 1
typedef int KeyType;
//静态查找表的顺序存储结构
typedef struct
{
KeyType key;
int item;
}ElemType;
typedef struct
{
ElemType *elem;//数据元素存储空间基址,建表时按实际升序分配,0号单元
//留空。
int length; //表长度
}SSTable;
typedef struct
{
KeyType key;
int link;
}IdxType;
int MaxIndex;
typedef struct BiTNode
{
ElemType data;
BiTNode *lchild,*rchild; // 左右孩子指针
}BiTNode,*BiTree;
typedef BiTree SOSTree; // 次优查找树采用二叉链表的存储结构
//输入
int InputS(SSTable &ST)
{
int N;
printf("Please input the number of data:\n");
scanf("%d",&N);
printf("Please input the %d's data:\n",N);
ST.elem=(ElemType*)calloc(N+1,sizeof(ElemType));//动态生成N个数据单元空间,
//0号单元不用
if(!ST.elem) printf("error");
for(int i=1;i<=N;i++)
{
printf("Please input the %d's data:\n",i);
scanf("%d",&ST.elem[i].key);
printf("Please input the %d's item:\n",i);
scanf("%d",&ST.elem[i].item);
}//for
ST.length=N;
return OK;
}//InputS
//排序
void Ascend(SSTable &ST)
{
//重建静态查找表为按关键字非降序排序
int i,j,k;
for(i=1;i<ST.length;i++)
{
k=i;
ST.elem[0]=ST.elem[i];
for(j=i+1;j<=ST.length;j++)
if(ST.elem[j].key<ST.elem[0].key)
{
k=j;
ST.elem[0]=ST.elem[j];
}//if
if(k!=i)
{
ST.elem[k]=ST.elem[i];
ST.elem[i]=ST.elem[0];
}//if
}//for
}//Ascend
int Creat_Ord(SSTable &ST)
{ // 操作结果: 构造一个含n个数据元素的静态按关键字非降序查找表ST
// 数据来自全局数组r
int f;
f=InputS(ST);
if(f)
Ascend(ST);
return f;
}
//顺序查找
int Search_Seq(SSTable ST,KeyType key)
{
int i;
ST.elem[0].key=key;
for(i=ST.length;ST.elem[i].key!=key;--i) ;
return i;
}//Search_Seq
//折半查找
int Search_Bin(SSTable ST,KeyType key)
{
int low,high,mid;
low=1;
high=ST.length;
while(low<=high)
{
mid=(low+high)/2;
if(ST.elem[mid].key==key) return mid;
else if(key<ST.elem[mid].key) high=mid-1;
else low=mid+1;
}//while
return 0;
}//Search_Bin
//分块查找
int IdxSerch(SSTable ST,IdxType index[],int b,KeyType key)
{ //分块查找关键字为k的记录,索引表为index[0..b-1]
int low=0,high=b-1,mid,i;
int s=ST.length/b; //每块记录个数
while(low<=high)
{ //在索引表中进行折半查找,找到的位置放在low中
mid=(low+high)/2;
if(index[mid].key<key) low=mid+1;
else high=mid-1;
}//while
if(low==b) low--;
if(low<=b) //在顺序表中顺序查找
{
for(i=index[low].link;(i<=index[low].link+s-1)&&(i<=ST.length);i++)
{
//printf(" %d %d",ST.elem[i].key,key);
if(ST.elem[i].key==key) return i;
}//for
return -1;
}//if
else return -1;
}//IdsSerch
//释放
int Destroy(SSTable &ST)
{
// 初始条件: 静态查找表ST存在。操作结果: 销毁表ST
free(ST.elem);
ST.elem=NULL;
ST.length=0;
return OK;
}
int IdxSerchIt(SSTable &ST,KeyType s)
{
int i;
printf("input the MaxIndex of index table:\n");
scanf("%d",&MaxIndex); //注意对,MAxIndex的选取有讲究,不然会出
//现结果不对的情况。
IdxType index[MaxIndex];
int ince=1;
for(int j=0;j<MaxIndex;j++)
{
index[j].key=ST.elem[ince].key;
index[j].link=ince;
if(ince<ST.length) ince+=ST.length/MaxIndex;
}//for
i=IdxSerch(ST,index,MaxIndex,s);
return i;
}//IdxSerchIt
int SecondOptimal(BiTree &T, ElemType R[],int sw[],int low,int high)
{ //构造次优查找树
int i,j;
double min,dw;
i=low;
min=fabs(sw[high]-sw[low]);
dw=sw[high]+sw[low-1];
for(j=low+1;j<=high;++j)
if(fabs(dw-sw[j]-sw[j-1])<min)
{
i=j;
min=fabs(dw-sw[j]-sw[j-1]);
}
if(!(T=(BiTree)malloc(sizeof(BiTNode))))
return 0;
T->data=R[i];
if(i==low)
T->lchild=NULL;
else
SecondOptimal(T->lchild,R,sw,low,i-1);
if(i==high)
T->rchild=NULL;
else
SecondOptimal(T->rchild,R,sw,i+1,high);
return OK;
}
void FindSW(int sw[],SSTable ST)
{ // 按照有序表ST中各数据元素的Weight域求累计权值表sw
int i;
sw[0]=0;
for(i=1;i<=ST.length;i++)
sw[i]=sw[i-1]+ST.elem[i].item;
}
int CreateSOSTree(SOSTree &T,SSTable ST)
{ // 由有序表ST构造一棵次优查找树T
//ST的数据元素含有权域weight
int sw[ST.length+1]; // 累计权值表sw
if(ST.length==0)
T=NULL;
else
{
FindSW(sw,ST); // 按照有序表ST中各数据元素的Weight域求累计权值表sw
SecondOptimal(T,ST.elem,sw,1,ST.length);
}
return OK;
}
int Search_SOSTree(SOSTree &T,KeyType key)
{ // 在次优查找树T中查找关键字等于key的元素。找到则返回OK,否则返回FALSE
while(T) // T非空
if(T->data.key==key)
return OK;
else if(T->data.key>key)
T=T->lchild;
else
T=T->rchild;
return 0; // 顺序表中不存在待查元素
}
void SearchIt(SSTable &ST)
{
int s,i;
SOSTree T;
printf("请输入要查找的数据:\n ");
scanf("%d",&s);
i=Search_Seq(ST,s); // 顺序查找
//i=Search_Bin(ST,s);//折半查找
//i=IdxSerchIt(ST,s);//分块查找
//CreateSOSTree(T,ST);//创建静态查找树
//Search_SOSTree(T,s);//静态查找树查找
if(i)
printf("%d: %d",i,ST.elem[i].key);
else
printf("没找到\n");
}
int main()
{
SSTable ST;
Creat_Ord(ST);
SearchIt(ST);
return OK;
}
五、算法的复杂度分析
1、简单顺序查找
从顺序查找过程可见(不考虑越界比较),顺序查找不论给定值k为何,若第1个记录符合给定值,只要比较1次。若第n个记录符合给定值,要比较n次,即ci=i。若每个记录的查找概率相等,且每次查找都是成功的。则在等概率的情况下,顺序查找的平均查找长度为:
2、折半查找
折半查找算法的计算复杂性可以用二叉树来进行分析。我们把当前查找区间的中间位置上的记录作为根。左子表和右子表中的记录分别作为根的左子树和右子树。由此得到的二叉树,称为描述二分查找的判定树或比较树。
树中每个结点表示一个记录,结点的编号为该记录在表中的位置。找到一个记录的过程就是走了一条从根结点到与该记录结点的路径。和给定值进行比较的次数正好是该结点所在的层次数。折半查找在查找成功时和给定值进行比较的关键字的个数至多为floor(log2n)+1。同理,查找不成功的过程也是走了一条从根结点到某一个终端结点的路径,其所用的比较次数等于树的深度。
折半查找只适用有序顺序存储结构,不适于线性链表结构。为保持表的有序性,在顺序结构里插入和删除都必须移动大量的记录。折半查找特别适用于那种一经建立就很少改动,而又经常需要查找的线性表。
3、分块查找(也叫索引顺序表)
由于分块查找实际上是两次查找过程,所以分块查找的平均查找长度是:查找索引表确定给定值所在块内的平均查找长度ASLb与在块的查找关键值的平均查找长度ASLk之和。即ASL=ASLb+ASLk。
4、静态树表的查找
其查找过程类似折半查找。其平均查找长度和Logn成正比。当在记录中查找概率不等时,可用次优查找树。
五、涉及函数
1、void *calloc(unsigned num, unsigned size),分配一块内存,num和size分配内存的数量参数,实际字节等于num*size。头文件为alloc.h。