查找表(search table)是由同一类型的数据元素(或记录)构成的集合。
对查找表进行的操作有:
(1)查询某个“特定的”数据元素是否存在查找表中。
(2)检索某个“特定的”数据元素的各种属性。
(3)在查找表中插入一个数据元素。
(4)从查找表中删除某个数据元素。
只做查询的操作的查找表称为静态查找表(Static Search Table)。
若在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已存在的某个数据元素,则称此查找表为动态查找表(Dynamic Search Table)。
关键字(Key)是数据元素(或记录)中某个数据项的值,用它可以标识一个数据元素(或记录),若此关键字可以唯一的标识一条记录,则称此关键字为主关键字(Primary Key),其他的用于识别若干记录的关键字称为次关键字(Secondary Key)。当数据元素只有一个数据项时,其关键字即为该数据元素的值。
查找(Searching)根据给定的某个值,在查找表中确定一个其关键字等于给定值的记录或数据元素。若表中存在这样的记录,则查找是成功的,若不存在,则查找不成功,给出“空”记录或空指针。
典型的关键字类型说明可以是:
typedef float KeyType; //实型 typedef int KeyType; //整型 typedef char *KeyType; //字符型
数据元素类型定义为:
typedef struct{ KeyType key; //关键字域 ... //其他 }
对两个关键字的比较的宏定义如下:
//--对数值型关键字 #define EQ(a,b) ((a) == (b)) #define LT(a,b) ((a) < (b)) #define LQ(a,b) ((a) <= (b)) ... //--对字符型关键字 #define EQ(a,b) (!strcmp((a),(b))) #define LT(a,b) (strcmp((a),(b)) < 0) #define LQ(a,b) (strcmp((a),(b)) <= 0)
1 静态查找表
抽象数据类型静态查找表定义如下:
ADT StaticSearchTable{ 数据对象D:D是具有相同特性的数据元素的集合,各个数据元素均含有类型相同的,可唯一标识数据元素的关键字。 数据关系R:数据元素同属一个集合。 基本操作P: Create(&ST,n); 操作结构:构造一个含n个数据元素的静态查找表ST。 Destroy(&ST); 初始条件:静态查找表ST存在。 操作结果:销毁表ST。 Search(ST,key); 初始条件:静态茶渣表ST存在,key为和关键字类型相同的给定值。 操作结果:若ST中存在其关键字等于key的数据元素,则函数值为该元素的值或在表中的位置,否则为“空”。 Traverse(ST,visit()); 初始条件:静态查找表ST存在,visit是对元素操作的应用函数。 操作结果:按某种次序对ST的每隔元素调用函数visit()一次。 }ADT StaticSearchTable;
1.1 顺序表的查找
//----------- 静态查找表的顺序存储结构------------ typedef struct{ Element *elem;//数据元素存储空间基址 int length; }SSTable;
顺序查找表的实现(Sequential Search):
从表中最后一个记录开始,逐个进行记录的关键字和给定值的比较,若某个记录的关键字和给定值比较成功,则查找成功,反之,直到第一个记录,仍不能成功比较,则查找不成功。
int search_seq(SSTable ST,KeyType key){ int i; //在顺序表ST中顺序查找其关键字=key的数据元素, //若找到,则函数值为该元素在表中的位置,否则为0. ST.elem[0].key = key;//哨兵 for(i=ST.length;!ST.item[i].key == key;--i) //从后往前找 return i; //找不到时,i =0 }//search_seq
查找操作的性能分析:
衡量一个算法好坏的量度有3条:
时间复杂度
空间复杂度(衡量算法的数据结构所占存储以及大量的附加存储)
算法的其他性能
通常以”其关键字和给定值进行过比较的记录个数的平均值“作为衡量查找算法好坏的依据。
平均查找长度(Average Search Length):为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望。
(1) 等概率的情况下顺序查找成功的平均查找长度为:
(n+1)/2
(2) 查找不成功的平均查找长度为
n+1
(3) 当查找不成功的概率不可以忽略时,假设查找成功和查找不成功的概率相同,则顺序查找的平均查找长度为:
3(n+1)/4
顺序查找的缺点:当n较大时,查找效率很低。
1.2 有序表的查找
折半查找(Binary Search):先确定待查记录所在的范围,然后逐步缩小范围直到找到或找不到记录为止。
顺序查找的查找过程如下:
(1) 确定待查记录所在的范围,设区间范围为[low,high];
(2) 求出处于该区间中间位置的值mid,mid = [low+high]/2;
(3) 将给定的key值与有序表中第mid个记录的关键字ST.item[mid].key相比较
(3.1) 若key == ST.item[mid].key,则查找成功
(3.2) 若key < ST.item[mid].key,则high=mid-1,并判断high是否小于low,若high<low,则查找不成功,否则,待查记录还是可能在新的区间[low,high]中,继续执行步骤(2)
(3.3) 若key > ST.item[mid].key,则low = mid + 1,判断high是否小于low,若high<low,则查找不成功,否则,待查记录还是可能在新的区间[low,high]中,继续执行步骤(2)。
算法实现如下:
int binarySearch(STable ST,KeyType key){ int mid,low=0,high=ST.length; while(low <=high){ mid = (low+high)/2; if(ST.item[mid].key == key) return mid; elseif(ST.item[mid].key < key) low = mid + 1; else high = mid - 1; } return 0; }
折半查找的过程可以用二叉树表示,
判定树即描述查找过程的二叉树
折半查找时与给定值进行比较的次数最多为[log(2)n]+1次
(1) 查找成功时的平均查找长度:
ASL = (n+1)/n * log(2)(n+1) - 1
当n>50时,简化为
ASL = log(2)(n+1)-1
(2) 查找不成功时的平均查找长度
uASL = log(2)(n+1)
折半查找的优点:查找效率比顺序查找的高
缺点:只能适用于有序表,且顺序存储结构,不能用于线性链表
1.3 索引顺序表的查找
索引顺序表包括存储数据的顺序表(称为主表)和索引表两部分。顺序表被分为若干子表(又称块),整个顺序表有序或分块有序。将每个子表中的最大关键字取出,再加上指向该关键字记录所在子表第一个元素的指针,就构成一个索引项,将这些索引项按关键字增序排列,就构成了索引表。
对于索引顺序表可按分块进行查找,过程分为两个阶段:
(1) 确定待查记录所在的子表(块),由于索引表是按关键字有序排列的,对于索引表可按折半查找,若待查记录关键字的key值,<=第i个子表最大关键字,且大于第i-1个子表的最大关键字,即K(i-1) < key < K(i),则待查关键字位于第i个子表中。
(2) 在已确定的子表中确定待查记录的位置,由于子表中的记录不一定按关键字有序排列,只能按顺序查找法查找。
索引表类型定义如下:
#define MAXLEN <索引表最大长度> typedef int keytype; typedef struct{ keytype key; int link; }IndexType;//索引项数据类型 typedef IndexType IDX[MAXLEN+1];//索引表类型,下标从1开始
索引表查找算法:
IndxSearch(IDX idx,int b,STable,keytype key){ int s = ST.length / b; //b为索引表中元素个数,s为每个子表中元素个数 int i,j; for(i=1;i<b+1;i++) if(key <= idx[i]) break; if(i == b+1) return 0;//从索引表中查找失败 //注意,此处是从idx[i].link开始,不能设置0哨兵,只能用if判断j == idx[i].link-1 for(j = idx[i].link+s-1;key!=ST.item[j].key;j--); if(j == idx[i].link-1) return 0; else return j; }
一般情况下,为进行分块查找,可以将长度为n的表均匀分成b块,每块还有s个元素,即b = [n/s],如果索引表采用顺序查找的方式,则查找成功时的平均查找长度为:
(s+b+2)/2
如果索引表采用折半查找,则成功时的平均查找长度为:
log(2)(n/s+1)+s/2