【C 数据结构】查找(一)静态查找表

一、查找表
查找表是由同一类型的数据元素或记录构成的集合。

对查找表经常进行的操作有:
⑴查询某个“特定的”数据元素是否在查找表中;
⑵检索某个“特定的”数据元素的各种属性;
⑶在查找表中插入一个数据元素;
⑷从查找表中删去某个数据元素。

若对查找表只作前两种统称为“查找”的操作,则称此类查找查找表为静态查找表。若在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已存在的某个数据元素,则称此类表为动态查找表

重要概念:
查找根据给定的值,在查找表中确定一个其关键字等于给定值的记录或数据元素;
⑵关键字是数据元素或记录中某个数据项的值,用它可以表示一个数据元素或记录
⑶若关键字可以唯一标识一个记录,则称为主关键字,反之则称为次关键字

二、静态查找表
顺序查找较为简单,这里给出一段使用了哨兵元素优化的查找函数代码:

typedef int Keytype; //别名可用作与外部数据类型的接口,需要时直接修改该部分定义即可不需要“大动干戈”
typedef struct{
	Keytype key;
}Elemtype;

typedef struct{
	Elemtype *elem;
	int	 length;
}SSTable;
int Search_seq(SSTable ST, Keytype key){
	//从后往前逆序查找关键字为key的元素,找到则返回位置下标,否则为0
	//tip:表头ST.elem[0]用作哨兵不用作其他数据存储
	ST.elem[0].key = key;
	int i = ST.length;
	for( ; ST.elem[i].key != key; --i);
	
	return i;
}//end of Search_seq

简析:在顺序查找过程中设立一个哨兵元素,目的在于免去查找过程中每一步都要进行的 i > 0 的比较。这个小小的程序设计技巧上的改进,在数据量较大的时候能极大提高效率。
链式查找同理即得,在此不做扩展

三、查找操作的性能分析
通常衡量一个算法好坏的度量有3条:时间复杂度(衡量算法执行的时间量级)、空间复杂度(衡量算法的数据结构所占存储以及大量的附加存储)和算法的其他性能。但对于查找算法来说,通常只需要一个或几个辅助空间,且其基本操作是“将记录的关键字和给定值进行比较”,因此,通常以“其关键字和给定值进行过比较的记录个数的平均值”作为衡量查找算法好坏的依据

平均查找长度:为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值。

对于含有 n 个记录的表,查找成功时的平均查找长度为 A S L    =    ∑ i = 1 n P i C i ASL\;=\;\sum_{i=1}^nP_iC_i ASL=i=1nPiCi,其中 P i P_i Pi为查找表中第 i 个记录的概率,且 ∑ i = 1 n P i    =    1 \sum_{i=1}^nP_i\;=\;1 i=1nPi=1; C i C_i Ci为找到表中关键字与给定值相等的第 i 个记录时,和给定值已进行过比较的关键字个数。从顺序查找的过程可见, C i C_i Ci取决于所查记录在表中的位置。 在这里,假设 n = ST.length ,则顺序查找的平均查找长度为 A S L    =    n P 1    +    ( n − 1 ) P 2    +    … …    + 2 P n − 1    +    P n ASL\;=\;nP_{1_{}}\;+\;(n-1)P_{2\;}+\;\dots\dots\;+2P_{n-1}\;+\;P_n ASL=nP1+(n1)P2++2Pn1+Pn,假设每个记录的查找概率相等即 P i P_i Pi = 1/n,那么在等概率情况下顺序查找的平均查找长度为 A S L s s    =    ∑ i = 1 n P i C i    =    1 n ∑ i = 1 n ( n − i + 1 )    =    n + 1 2 ASL_{ss}\;=\;\sum_{i=1}^nP_iC_i\;=\;\frac1n\sum_{i=1}^n(n-i+1)\;=\;\frac{n+1}2 ASLss=i=1nPiCi=n1i=1n(ni+1)=2n+1. 上述对平均查找长度的讨论是在 ∑ P i i = 1 n \overset n{\underset{i=1}{\sum P_i}} i=1Pin = 1的前提下进行的,即默认每次查找都是成功的,而对于查找不成功的情况,无论给定何种 key ,进行关键字比较的次数均为 n+1。假设查找成功与不成功的可能性相同,对每个记录的查找概率也相等,则 P i    =    1 2 n P_i\;=\;\frac1{2n} Pi=2n1,此时顺序查找的平均查找长度为 A S L s s    =    1 2 n ∑ i = 1 n ( n − i + 1 )    +      1 2 ( n + 1 )    =    3 4 ( n + 1 ) ASL_{ss}\;=\;\frac1{2n}\sum_{i=1}^n(n-i+1)\;+\;\;\frac12(n+1)\;=\;\frac34(n+1) ASLss=2n1i=1n(ni+1)+21(n+1)=43(n+1).

四、有序表的查找

1. 折半查找
折半查找是以处于区间中间位置记录的关键字和给定值比较,若相等则查找成功,不等则缩小查找范围,直至新的区间中间位置记录的关键字等于给定值或者查找区间的大小小于零(此时查找不成功)为止。这个过程比较好理解,直接贴出代码:

int Search_Bin(SSTable ST, Keytable key){
//在有序表ST中折半查找关键字为key的数据元素,找到则返回其位置,否则为0
	int low=1, high=ST.length;			//初始查找区间
	int mid = (low + high) /2;
	while(low <= high){
		if( key== ST.elem[mid].key )	//已找到查找元素
			return mid;
		else if( key < ST.elem[mid].key )
			high = mid-1;		//查找元素的关键字小于中间位置的关键字,转到前半区间继续查找
		else					//查找元素的关键字大于中间位置的关键字,转到后半区间继续查找
			low = mid+1;
	}//end of while
	return 0;					//顺序表中不存在查找元素
}//Search_Bin		

查找成功时,折半查找的平均查找长度: A S L b s    = ∑ i = 1 n P i C i    =    1 n    ∑ j = 1 h j ⋅ 2 j − 1    =    n + 1 n    log ⁡ 2 ( n + 1 )    − 1 ASL_{bs}\;=\sum_{i=1}^nP_iC_i\;=\;\frac1n\;\sum_{j=1}^hj\cdot2^{j-1}\;=\;\frac{n+1}n\;\log_2\left(n+1\right)\;-1 ASLbs=i=1nPiCi=n1j=1hj2j1=nn+1log2(n+1)1.

2. 静态树表查找
上述讨论是在等概率的前提下进行的,当概率不等时,折半查找的性能不一定是最优的。如果只考虑查找成功的情况,则使查找性能达到最佳的判定树是其带权内路径长度之和PH值取最小时的二叉树。

P H    =    ∑ i = 1 n ω i h i PH\;=\;\sum_{i=1}^n\omega_ih_i PH=i=1nωihi 其中 n 为二叉树上的结点个数(即有序表的长度), h i h_i hi为第 i 个结点在二叉树的层次,结点的权 ω i    =    c p i \omega_i\;=\;cp_i ωi=cpi(i = 1,2,3,…,n), p i p_i pi为结点的查找概率,c为常量。

但由于构造静态最优查找树花费的时间代价较高,因此在这里仅介绍一种构造次优查找树的算法实现。(最优除了构造的时间代价高之外,次优的性能与最优相差无几,大量实验表明,差值不超过3%)

现构造一棵二叉树,使这棵二叉树的带权内路径长度PH值在所有具有相同权值的二叉树中近似为最小,称这类二叉树为次优查找树。构造方法如下:(这里给出原理,后续给出实例分析,推荐二者结合较容易理解)

已知一个按关键字有序的记录序列( r l r_l rl, r l + 1 r_{l+1} rl+1, … , r h r_h rh),其中 r l r_l rl.key < r l + 1 r_{l+1} rl+1.key < … < r h r_h rh.key 且与每个记录相应的权值为 ω l \omega_l ωl, ω l + 1 \omega_{l+1} ωl+1, … , ω h \omega_h ωh
取序列中第 i i i ( l ≤ i ≤ h l\leq i\leq h lih) 个记录构造根结点,使得 △ P i    =    ∣ ∑ j = i + 1 h ω i    −    ∑ j = l i − 1 ω j    ∣ \triangle P_i\;=\;\left|{\textstyle\sum_{j=i+1}^h}\omega_i\;-\;{\textstyle\sum_{j=l}^{i-1}}\omega_{j\;}\right| Pi=j=i+1hωij=li1ωj。取最小的 △ P i    \triangle P_i\; Pi,然后分别对子序列 { r l ,    r l + 1 ,    . . .    ,    r i − 1 } \{r_l,\;r_{l+1},\;...\;,\;r_{i-1}\} {rl,rl+1,...,ri1} { r i + 1 ,    . . .    ,    r h } \{r_{i+1},\;...\;,\;r_h\} {ri+1,...,rh} 构造两棵次优查找树,并分别设为根结点 r i r_i ri的左右子树。
为方便计算 △ P i \triangle P_i Pi,引入累计权值和 s ω i    =    ∑ j = l i ω j s\omega_i\;=\;{\textstyle\sum_{j=l}^i}\omega_j sωi=j=liωj,并设 ω l − 1    =    s ω l − 1    =    0 \omega_{l-1}\;=\;s\omega_{l-1}\;=\;0 ωl1=sωl1=0, 则有 { s ω i − 1    −    s ω l − 1    =    ∑ j = l i − 1 ω j s ω h         −    s ω i            = ∑ j = i + 1 h ω j \left\{\begin{array}{l}s\omega_{i-1\;}-\;s\omega_{l-1}\;=\;{\textstyle\sum_{j=l}^{i-1}}\omega_j\\s\omega_{h\;}\;\;-\;s\omega_i\;\;\;\;\;={\textstyle\sum_{j=i+1}^h}{\textstyle{\scriptstyle\omega}_j}\end{array}\right. {sωi1sωl1=j=li1ωjsωhsωi=j=i+1hωj
△ P i    =    ∣ ( s ω h    −    s ω i    )    −    ( s ω i − 1    −    s ω l − 1 ) ∣    =      ∣ ( s ω h    +    s ω l − 1    )    −    s ω i    −    s ω i − 1 ∣ \triangle P_i\;=\;\left|(s\omega_h\;-\;s\omega_i\;)\;-\;(s\omega_{i-1}\;-\;s\omega_{l-1})\right|\;=\;\;\left|(s\omega_h\;+\;s\omega_{l-1}\;)\;-\;s\omega_i\;-\;s\omega_{i-1}\right| Pi=(sωhsωi)(sωi1sωl1)=(sωh+sωl1)sωisωi1

下面给出实例来具体分析该构造方法的操作流程,已知含9个关键字的有序表及其相应的权值为:在这里插入图片描述
按上述流程构造次优查找树:在这里插入图片描述
构造得到的次优查找树为:
在这里插入图片描述
3. 索引顺序查找(分块查找)
索引顺序查找时顺序查找的一种改进方法,在此查找法中,除顺序表本身还需建立一个“索引表”。其查找过程分为两步,首先确定待查记录所在的块(子表),然后在块中顺序查找。由于该方法本质上仍为顺序表,在此不做过多扩展。

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页