查找
7.2 顺序查找和折半查找
顺序查找
一般线性表
从线性表的一端开始,逐个检查关键字是否满足给定的条件。若查找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置:若己经查找到表的另一端,但还没有查找到符合给定条件的元素,则返回查找失败的信息。
typedef struct{
ELemType *elem;
int TableLen;
}SSTable;
int Search_Seq(SSTable ST, ElemType key){
ST.elem[0]=key; //哨兵
for(i=ST.TableLen;ST.elem[i]!=key;--i)
return i;
}
哨兵可以使得程序不用判断数组是否越界。
一般线性表顺序查找的平均长度为
A
S
L
成
功
=
∑
i
=
1
n
P
(
n
−
i
+
1
)
ASL_{成功}=\sum_{i=1}^{n}P(n-i+1)
ASL成功=∑i=1nP(n−i+1)
当每个元素查找概率相等时
A
S
L
成
功
=
∑
i
=
1
n
P
(
n
−
i
+
1
)
=
n
+
1
2
ASL_{成功}=\sum_{i=1}^{n}P(n-i+1)=\frac{n+1}{2}
ASL成功=∑i=1nP(n−i+1)=2n+1
当查找不成功时,平均长度为
A
S
L
不
成
功
=
n
+
1
ASL_{不成功}=n+1
ASL不成功=n+1
优点
1)对数据存储没有要求;
2)对记录有序性没有要求。
缺点
1)当n较大时,平均查找长度较大,效率低。
有序表
若在查找之前就己经知道表是关键字有序的,则查找失败时可以不用再比较到表的另一端就能返回查找失败的信息,从而降低顺序查找失败的平均查找长度。
假设表
L
L
L是按关键字从小到大排列的,查找的顺序是从前往后,待查找元素的关键字为
k
e
y
key
key,当查找到第
i
i
i个元素时,发现第
i
i
i个元素对应的关键字小于
k
e
y
key
key但第
i
+
1
i+1
i+1个元素对应的关键字大于
k
e
y
key
key,这时就可返回查找失败的信息,因为第
i
i
i个元素之后的元素的关键字均大于
k
e
y
key
key,所以表中不存在关键字为
k
e
y
key
key的元素。
有序表顺序查找不成功的平均长度为
A
S
L
不
成
功
=
∑
j
=
1
n
q
j
(
l
j
−
1
)
=
1
+
2
+
.
.
.
+
n
+
n
n
+
1
=
n
2
+
n
n
+
1
ASL_{不成功}=\sum_{j=1}^{n}q_j(l_j-1)=\frac{1+2+...+n+n}{n+1}=\frac{n}{2}+\frac{n}{n+1}
ASL不成功=∑j=1nqj(lj−1)=n+11+2+...+n+n=2n+n+1n,式中,
q
j
q_j
qj是到达第
j
j
j个失败结点的概率。
※式中
1
+
2
+
.
.
.
+
n
+
n
n
+
1
\frac{1+2+...+n+n}{n+1}
n+11+2+...+n+n是因为无论落在最后两个数据的区间内还是大于最后一个数据,查找长度都为
n
n
n。
折半查找
又称二分查找,仅适用于有序的顺序表。
折半查找的基本思想:首先将给定值
k
e
y
key
key与表中中间位置的元素比较,若相等,则查找成功,
返回该元素的存储位置;若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分
(例如,在查找表升序排列时,若给定值key大于中间元素.则所查找的元素只可能在后半部分)。然后在缩小的范围内继续进行同样的查找,如此重复,直到找到为止,或确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。算法如下:
int Binary_Search(SeqList L, ElemType key){
int low=0, high=L.TableLen-1, mid;
while(low<=high){
mid=(high+low)/2;
if(L.elem[mid]==k)
return mid;
else if(L.elem[mid]>key)
high=mid-1;
else
low=mid+1;
}
return -1; //查找失败
}
折半查找可用称为判定树的二叉树描述。
圆形结点:查找表一个记录,结点中的值为该记录的关键字值;
方形叶结点:表示查找不成功的情况。
查找成功时的查找长度为从根结点到目的结点的路径上的结点数;
杳找不成功时的查找长度为从根结点到对应失败结点的父结点的路径上的结点数;
每个结点值均大于其左子结点值,且均小于于其右子结点值。
若有序序列有
n
n
n个元素,则对应的判定树有
n
n
n个圆形的非叶结点和
n
+
1
n+1
n+1个方形的叶结点。
显然,判定树是一棵平衡二叉树。

查找成功的平均长度:
A
S
L
=
1
n
∑
i
=
1
n
l
i
=
1
n
(
1
×
1
+
2
×
2
+
.
.
.
+
h
×
2
h
−
1
)
=
n
+
1
n
l
o
g
2
(
n
+
1
)
≈
l
o
g
2
(
n
+
1
)
−
1
ASL=\frac{1}{n}\sum_{i=1}^nl_i=\frac{1}{n}(1×1+2×2+...+h×2^{h-1})=\frac{n+1}{n}log_2^{(n+1)}≈log_2^(n+1)-1
ASL=n1∑i=1nli=n1(1×1+2×2+...+h×2h−1)=nn+1log2(n+1)≈log2(n+1)−1
其中
h
h
h是树的高度,并且元素个数为
n
n
n时树高
h
=
⌈
l
o
g
2
(
n
+
1
)
⌉
h=\lceil log_2^{(n+1)}\rceil
h=⌈log2(n+1)⌉
时间复杂度为
O
(
l
o
g
2
n
)
O(log_2^n)
O(log2n),比较次数最多不会超过树的高度。
优点
1)效率高于顺序查找
缺点
1)该方法仅适合于顺序存储结构,不适合于链式存储结构
2)要求关键字有序
分块查找
分块查找又称索引順序查找。它吸取了顺序查找和折半查找各自的优点,既有动态结构,又
适于快速查找。
分块查找的基本思想将查找表分为若十子块。块内的元素可以无序,但块之间是有序的,即第一个块中的最大关键字小于第二个块中的所有记录的关键字,第二个块中的最大关键字小于
第三个块中的所有记录的关键字,以此类推。再建立一个索引表,索引表中的每个元素含有各块
的最大关腱字和各块中的第一个元素的地址,索引表按关键字有序排列。
分块查找的过程分为两步:
1)在索引表中确定待查记录所在的块,可以顺序查找或折半查找索引表;
2)在块内顺序查找。

其平均查找长度为
A
S
L
=
L
I
+
L
S
ASL=L_I+L_S
ASL=LI+LS
将长度为
n
n
n的查找表均匀分为
b
b
b块时,每块有
s
s
s个记录,在等概率情况下,
若在块内和索引表中均采用顺序查找,则其平均查找长度为
A
S
L
=
L
I
+
L
S
=
b
+
1
2
+
s
+
1
2
=
s
2
+
2
s
+
n
2
s
ASL=L_I+L_S=\frac{b+1}{2}+\frac{s+1}{2}=\frac{s^2+2s+n}{2s}
ASL=LI+LS=2b+1+2s+1=2ss2+2s+n$
若对索引表采用折半查找,则平均查找长度为
A
S
L
=
L
I
+
L
S
=
⌈
l
o
g
2
(
n
+
1
)
⌉
+
s
+
1
2
ASL=L_I+L_S=\lceil log_2^{(n+1)}\rceil+\frac{s+1}{2}
ASL=LI+LS=⌈log2(n+1)⌉+2s+1
习题7.2
选择题
- 当二叉排序树为单支树时,其查找长度为 O ( n ) O(n) O(n)(Q9)
- 对有 n n n个记录的索引表分块查找时,最理想的块长是 n \sqrt{n} n
简答题

1)查找失败
有序表:遇见第一个大于关键字的值即停止,平均查找失败长度为
A
S
L
失
败
=
1
+
2
+
.
.
.
+
n
2
=
n
+
1
2
ASL_{失败}=\frac{1+2+...+n}{2}=\frac{n+1}{2}
ASL失败=21+2+...+n=2n+1;
无序表:
A
S
L
失
败
=
n
ASL_{失败}=n
ASL失败=n
2)查找成功,且表中只有一个关键字给定值k的元素。
相同,均为
A
S
L
成
功
=
∑
i
=
1
n
P
(
n
−
i
+
1
)
=
n
+
1
2
ASL_{成功}=\sum_{i=1}^{n}P(n-i+1)=\frac{n+1}{2}
ASL成功=∑i=1nP(n−i+1)=2n+1
3)查找成功,且表中有若干关键字等于给定值k的元素,要求一次查找能找出所有元素。
有序表:只要找到第一个相同就可以连续找到其他关键字相同的元素。
无序表:必须查找表中全部的元素才能找出相同关键字的元素。

3)
A
S
L
成
功
=
1
+
2
∗
2
+
3
∗
4
+
4
∗
7
14
=
45
14
ASL_{成功}=\frac{1+2*2+3*4+4*7}{14}=\frac{45}{14}
ASL成功=141+2∗2+3∗4+4∗7=1445
A
S
L
不
成
功
=
3
∗
1
+
4
∗
14
15
=
59
15
ASL_{不成功}=\frac{3*1+4*14}{15}=\frac{59}{15}
ASL不成功=153∗1+4∗14=1559

可以用k叉树来描述,k分查找发在查找成功时进行比较的关键字的个数最多不超过树的深度,而具有n个结点的k叉树的深度为
⌊
l
o
g
k
n
⌋
+
1
\lfloor log_k^n\rfloor+1
⌊logkn⌋+1,
k
k
k分查找法在查找成功时和给定的值进行比较的关键字个数至多为
⌊
l
o
g
k
n
⌋
+
1
\lfloor log_k^n\rfloor+1
⌊logkn⌋+1,即时间复杂度为
O
(
l
o
g
k
n
)
O(log_k^n)
O(logkn),同理,查找不成功时,和给定的值进行比较的关键字个数也至多为
⌊
l
o
g
k
n
⌋
+
1
\lfloor log_k^n\rfloor+1
⌊logkn⌋+1,时间复杂度也为
O
(
l
o
g
k
n
)
O(log_k^n)
O(logkn)。

1)如图,其中每个关键字下的数字为其查找成功时的关键字比较次数。

2)等查找概率下,平均每个关键字查找成功的概率为1/8n
A
S
L
成
功
=
1
8
n
∑
i
=
0
8
n
−
1
C
i
=
1
8
n
(
∑
i
=
1
n
i
+
∑
i
=
2
n
+
1
i
+
∑
i
=
3
n
+
2
i
+
∑
i
=
4
n
+
3
i
)
=
1
8
n
(
∑
i
=
1
n
(
i
+
(
i
+
1
)
+
2
(
i
+
2
)
+
4
(
i
+
3
)
)
=
1
8
n
∑
i
=
1
n
(
8
i
+
17
)
=
1
n
∑
i
=
1
n
i
+
17
8
=
n
+
1
2
+
17
8
ASL_{成功}=\frac{1}{8n}\sum_{i=0}^{8n-1}C_i=\frac{1}{8n}(\sum_{i=1}^ni+\sum_{i=2}^{n+1}i+\sum_{i=3}^{n+2}i+\sum_{i=4}^{n+3}i)=\frac{1}{8n}(\sum_{i=1}{n}(i+(i+1)+2(i+2)+4(i+3))=\frac{1}{8n}\sum_{i=1}^n(8i+17)=\frac{1}{n}\sum_{i=1}^ni+\frac{17}{8}=\frac{n+1}{2}+\frac{17}{8}
ASL成功=8n1∑i=08n−1Ci=8n1(∑i=1ni+∑i=2n+1i+∑i=3n+2i+∑i=4n+3i)=8n1(∑i=1n(i+(i+1)+2(i+2)+4(i+3))=8n1∑i=1n(8i+17)=n1∑i=1ni+817=2n+1+817

1)元素按其查找概率降序排列时查找长度最小。顺序查找。
A
S
L
=
0.35
∗
1
+
035
∗
2
+
0.15
∗
3
+
0.15
∗
4
=
2.1
ASL=0.35*1+035*2+0.15*3+0.15*4=2.1
ASL=0.35∗1+035∗2+0.15∗3+0.15∗4=2.1
2)同(1)顺序查找,或者用二叉排序树(按首字母先后顺序)
A
S
L
=
0.15
∗
1
+
0.35
∗
2
+
0.35
∗
2
+
0.15
∗
3
=
2.0
ASL=0.15*1+0.35*2+0.35*2+0.15*3=2.0
ASL=0.15∗1+0.35∗2+0.35∗2+0.15∗3=2.0


int BinSeacrhRec(SSTable ST, ElemType key, int low, int high){
if(low>high)
return 0;
mid=(high+low)/2;
if(key>ST.elem[mid])
BinSeacrhRec(ST,key,mid+1,high);
else if(key<ST.elem[mid])
BinSeacrhRec(ST,key,low,mid-1);
else
return mid;
}

int SeqSrch(RcdType R[], ElemType k){
int i=0;
while((R[i].key!=k)&&(i<n))
i++; //找到k
if(i<n&&i>0){ //将k往前移
temp=R[i];
R[i]=R[i-1];
R[i-1]=temp;
}
else
return -1;
}
7.3 B树和B+树
B树及其基本操作
B树,又称多路平衡查找树,B树中所有结点的孩子个数的最大值称为B树的阶,通常用m
表示。一棵m阶B树或为空树,或为满足如下特性的m叉树:
1)树中每个结点至多有m棵子树,即至多含有m-1个关键字。
2)若根结点不是终端结点,则至少有两棵子树。
3)除根结点外的所有非叶结点至少有
⌈
m
/
2
⌉
\lceil m/2\rceil
⌈m/2⌉棵子树,即至少含有
⌈
m
/
2
⌉
−
1
\lceil m/2\rceil-1
⌈m/2⌉−1个关键字。
4)所有非叶结点的结构如下:其中
K
i
K_i
Ki为结点的关键字,且满足升序,
P
i
P_i
Pi为指向子树根结点的指针,且
P
i
−
1
P_{i-1}
Pi−1所指子树中所有结点的关键字均小于
K
i
K_i
Ki,
P
i
P_i
Pi的关键字均大于
K
i
K_i
Ki.
n
(
⌈
m
/
2
⌉
−
1
≤
n
≤
m
−
1
)
n(\lceil m/2\rceil-1≤n≤m-1)
n(⌈m/2⌉−1≤n≤m−1)为结点中关键字的个数。
,
5)所有的叶结点都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查
找判定树的查找失败结点,实际上这些结点不存在,指向这些结点的指针为空)。
B树是所有结点的平衡因子均等于0的多路平衡查找树。
B树的高度(磁盘存取次数)
讨论不包括不带信息的叶结点层。
若
n
≥
1
n≥1
n≥1,则对任意一棵包含
n
n
n个关键字、高度为
h
h
h、阶数为m的B树:
1)因为B树中每个结点最多有
m
m
m棵子树,
m
−
1
m-1
m−1个关键字,所以在一棵高度为
h
h
h的
m
m
m阶B树中关键字的个数应满足
n
≤
(
m
−
1
)
(
1
+
m
+
m
2
+
.
.
.
+
m
h
−
1
=
m
h
−
1
n≤(m-1)(1+m+m^2+...+m^{h-1}=m^h-1
n≤(m−1)(1+m+m2+...+mh−1=mh−1,因此有
h
>
l
o
g
m
(
n
+
1
)
h>log_m^{(n+1)}
h>logm(n+1).
2)若让每个结点中的关键字个数达到最少,则容纳同样多关键字的B树的高度达到最大。由B树的定义:第一层至少有1个结点:第二层至少有2个结点:除根结点外的每个非终端结点至少有
⌈
m
/
2
⌉
\lceil m/2\rceil
⌈m/2⌉棵子树,则第三层至少有
2
⌈
m
/
2
⌉
2\lceil m/2\rceil
2⌈m/2⌉个结点……第
h
+
1
h+1
h+1层至少有
2
(
⌈
m
/
2
⌉
)
h
−
1
2(\lceil m/2\rceil)^{h-1}
2(⌈m/2⌉)h−1个结点,注意到第
h
+
1
h+1
h+1层是不包含任何信息的叶结点。对于关键字个数为的B树,叶结点即查找不成功的结点为
n
+
1
n+1
n+1,由此有
n
+
1
≥
2
(
⌈
m
/
2
⌉
)
h
−
1
n+1≥2(\lceil m/2\rceil)^{h-1}
n+1≥2(⌈m/2⌉)h−1,即
h
≤
l
o
g
⌈
m
/
2
⌉
(
(
n
+
1
)
/
2
)
+
1
h≤log_{\lceil m/2\rceil}^{((n+1)/2)}+1
h≤log⌈m/2⌉((n+1)/2)+1。
B树的查找
在B树上进行查找与二叉查找树很相似,只是每个结点都是多个关键字的有序表,在每个结
点上所做的不是两路分支决定,而是根据该结点的子树所做的多路分支决定。
B树的查找包含两个基本操作:
①在B树中找结点;
②在结点内找关键字。
由于B树常存储在磁盘上,因此前一个查找操作是在磁盘上进行的,而后一个查找操作是在内存中进行的,即在找到目标结点后,先将结点信息读入内存,然后在结点内采用顺序查找法或折半查找法。
在B树上查找到某个结点后,先在有序表中进行查找,若找到则查找成功,一否则按照对应的
指针信息到所指的子树中去查找。
查找到叶结点时(对应指针为空指针),则说明树中没有对应的关键字,查找失败。
B树的插入
在B树中找到插入的位置后,并不能简单地将其添加到终端结点中,因为此时可能会导致整棵树不再满足B树定义中的要求。将关键字key插入B树的过程如下:
1)定位。利用前述的B树查找算法,找出插入该关键字的最低层中的某个非叶结点(在B
树中查找key时,会找到表示查找失败的叶结点;这样就确定了最底层非叶结点的插入位置。注意:插入位置一定是最低层中的某个非叶结点)。
2)插入。在B树中,每个非失败结点的关键字个数都在区间
[
⌈
m
/
2
⌉
−
1
,
m
−
1
]
[\lceil m/2\rceil-1, m-1]
[⌈m/2⌉−1,m−1]内。插入后的结点关键字个数小于m,可以直接插入;插入后检查被插入结点内关键字的个数,当插入后的结点关键字个数大于m-1时,须对结点进行分裂。
分裂
取一个新结点,在插入key后的原结点,从中间位置
(
⌈
m
/
2
⌉
−
1
)
(\lceil m/2\rceil-1)
(⌈m/2⌉−1)将其中的关
键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间
位置
(
⌈
m
/
2
⌉
−
1
)
(\lceil m/2\rceil-1)
(⌈m/2⌉−1)的结点插入原结点的父结点,若此时导致其父结点的关键字个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根结点为止,进而导致B树高度增1。

B树的删除
当被删关键字
k
k
k不在终端结点(最低层非叶结点)中时,可以用
k
k
k的前驱(或后继)
k
′
k'
k′来替
代
k
k
k,然后在相应的结点中删除
k
′
k'
k′,关键字
k
′
k'
k′必定落在某个终端结点中,则转换成了被关键字在终端节点的情形。

当被删关键字在终端结点(最低层非叶结点)中时,有下列三种情况:
1)直接删除关键字。若被删除关键字所在结点的关键字个数
≥
⌈
m
/
2
⌉
≥\lceil m/2\rceil
≥⌈m/2⌉,表明删除该关键字后仍满足B树的定义,则直接删去该关键字。
2)兄弟够借。若被删除关键字所在结点删除前的关键字个数
=
⌈
m
/
2
⌉
−
1
=\lceil m/2\rceil-1
=⌈m/2⌉−1,且与此结点相邻的右(或左)兄弟结点的关键字个数之
≥
⌈
m
/
2
⌉
≥\lceil m/2\rceil
≥⌈m/2⌉,则需要调整该结点、右(或左)兄弟结点及其双亲结点(父子换位法),以达到新的平衡。

3)兄弟不够借。若被删除关键字所在结点删除前的关键字个数
=
⌈
m
/
2
⌉
−
1
=\lceil m/2\rceil-1
=⌈m/2⌉−1,且此时与该结点相邻的左、右兄弟结点的关键字个数均
=
⌈
m
/
2
⌉
−
1
=\lceil m/2\rceil-1
=⌈m/2⌉−1,则将关键字删除后与左(或右)兄弟结点及双亲结点中的关键字进行合并。
在合并过程中,双亲结点中的关键字个数会减1。若其双亲结点是根结点且关键字个数减少
至0(根结点关键字个数为1时,有2棵子树),则直接将根结点删除,合并后的新结点成为根;
若双亲结点不是根结点,且关键字个数减少到均
=
⌈
m
/
2
⌉
−
2
=\lceil m/2\rceil-2
=⌈m/2⌉−2,则又要与它自己的兄弟结点进行调整或合并操作,并重复上述步骤,直至符合B树的要求为止。

B+树的基本概念
一棵m阶的B+树需满足下列条件;
1)每个分支结点最多有m棵子树(孩子结点)。
2)非叶根结点至少有两棵子树,其他每个分支结点至少有
⌈
m
/
2
⌉
\lceil m/2\rceil
⌈m/2⌉棵子树,
3)结点的子树个数与关键字个数相等。
4)所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小序排列,并且相邻叶结点按大小顺序相互链接起来。
5)所有分支结点(可视为索引的索引)中仅包含它的各个子结点(即下一级的索引块)中
关键字的最大值及指向其子结点的指针。
m阶的B+树与m阶的B树的主要差异如下:
1)在B+树中,具有n个关键字的结点只含有n棵子树,即每个关键字对应一棵子树:而在B树中,有”n关键字的结点含有n+1棵子树。
2)在B+树中,每个结点(非根内部结点)的关键字个数n的范围是
⌈
m
/
2
⌉
≤
n
≤
m
\lceil m/2\rceil≤n≤m
⌈m/2⌉≤n≤m(根结点:
1
≤
n
≤
m
1≤n≤m
1≤n≤m,在B树中,每个结点(非根内部结点)的关键字个数的范围是
⌈
m
/
2
⌉
−
1
≤
n
≤
m
−
1
\lceil m/2\rceil-1≤n≤m-1
⌈m/2⌉−1≤n≤m−1(根结点:
1
≤
n
≤
m
−
1
1≤n≤m-1
1≤n≤m−1).
3)在B+树中,叶结点包含信息,所有非叶结点仅起索引作用,非叶结点中的每个索引项只
含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。
4)在B+树中,叶结点包含了全部关键字,即在非叶结点中出现的关键字也会出现在叶结点中;而在B树中,叶结点包含的关键字和其他结点包含的关键字是不重复的。

习题7.3
选择题
- B树的叶结点对于查找失败的情况,对有 n n n个关键字的查找集合进行查找,失败可能性有 n + 1 n+1 n+1种,因此具有 n n n个关键字的 m m m阶B树,应该有 n + 1 n+1 n+1个叶结点。
- 一棵 m m m阶的B树中含有 n n n个关键字,则树的最大高度为 h ≤ l o g ⌈ m / 2 ⌉ ( ( n + 1 ) / 2 ) + 1 h≤log_{\lceil m/2\rceil}^{((n+1)/2)}+1 h≤log⌈m/2⌉((n+1)/2)+1,最小高度为 h ≥ l o g m ( n + 1 ) h≥log_{m}^{(n+1)} h≥logm(n+1)。
简答题
- 利用B树做文件索引时,若假设磁盘页块的大小是4000B(实际应是2的次幂,此处是
为了计算方便),指示磁盘地址的指针需要5B,现有20000000个记录构成的文件,每个
记录为200B,其中包括关键字5B
试问在这个采用B树作索引的文件中,B树的阶数应为多少?假定文件数据部分未按关键字有序排列,则索引部分需要占用多少磁盘页块?
根据B树的概念,一个索引结点应适应操作系统一次读写的物理记录大小,其大小应取不超
过但最接近一个磁盘页块的大小。假设B树为
m
m
m阶,一个B树结点最多存放
m
−
1
m-1
m−1个关键字(5B)
和对应的记录地址(5B)、
m
m
m个子树指针(5B)和1个指示结点中的实际关键字个数的整数(2B),
则有
(
2
×
(
m
−
1
)
【
关
键
字
和
对
应
记
录
地
址
】
+
m
【
指
针
】
)
×
5
+
2
≤
4000
(2×(m-1)【关键字和对应记录地址】+m【指针】)×5+2≤4000
(2×(m−1)【关键字和对应记录地址】+m【指针】)×5+2≤4000
计算结果为
m
≤
267
m≤267
m≤267。
一个索引结点最多可以存放
m
−
1
=
266
m-1=266
m−1=266个索引项,最少可以存放
⌈
m
/
2
⌉
−
1
=
133
\lceil m/2\rceil-1=133
⌈m/2⌉−1=133个索引项。
全部有
n
=
20000000
n=20000000
n=20000000个记录,每个记录占用空间200B,每个页块可以存放
4000
/
200
=
20
4000/200=20
4000/200=20个记录,则全部记录分布在20000000/20=1000000个页块中,最多需要占用
1000000
/
133
=
7519
1000000/133=7519
1000000/133=7519个磁盘页块作为B树索引,最少需要占用
1000000
/
266
=
3760
1000000/266=3760
1000000/266=3760个磁盘页块作为B树索引(注意B树与B+树的不同,B树所有对数据记录的索引项分布在各个层次的结点中,B+树所有对数据记录的索引项都在叶结点中)。
7.4 散列表
基本概念
散列函数
一个把查找表中的关键字映射成该关字对应的地址的函数,记为Hash(key)=Addr(这里的地址可以是数组下标、索引或内存地址等)。
散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为冲突,这些发生碰撞的不同关键字称为同义词。
散列表:根据关键字而直接进行访问的数据结构。也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系。
理想情况下,对散列表进行查找的时间复杂度为 O ( 1 ) O(1) O(1),即与表中元素的个数无关.下面分别介绍常用的散列函数和处理冲突的方法。
散列函数构造方法
1)定义域必须包含全部需要存储的关键字,值域依赖于散列表的大小或地址范围。
2)地址应该能等概率、均匀地分布在整个地址空间中,从而减少冲突。
3)应尽量简单。
直接定址法
直接取关键字的某个线性函数值为散列地址,散列函数为
H
(
k
e
y
)
=
k
e
y
H(key)=key
H(key)=key或
H
(
k
e
y
)
=
a
x
k
e
y
+
b
H(key)=axkey+b
H(key)=axkey+b
式中,a和b是常数。这种方法计算最简单,且不会产生冲突。它适合关键字的分布基本连续的情况,若关键字分布不连续,窄位较多,则会造成存储空间的浪费。
除留余数法
这是一种最简单、最常用的方法,假定散列表表长为m,取一个不大于m但最接近或等于m的质数p,利用以下公式把关键字转换成散列地址。散列函数为
H
(
k
e
y
)
=
k
e
y
H(key)=key%p
H(key)=key
除留余数法的关键是选好p使得每个关键字通过该函数转换后等概率地映射到散列空间上的任一地址,从而尽可能减少冲突的可能性。
数字分析法
设关键字是r进制数(如十进制数),而r个数在各位上出现的频率不一定相同.可能在某
些位上分布均匀一些,每种数码出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出现,此时应选取数码分布较为均匀的若干位作为散列地址。这种方法适合于己知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。
平方取中法
顾名思义,这种方法取关键字的平方值的中间几位作为散列地址。具体取多少位要视实际情
况而定.这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均匀,适用于关键字的每位取值都不够均匀或均小于散列地址所需的位数。
处理冲突的方法
开放定址法
可存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开放。其数学递推公式为
H
i
=
(
H
(
k
e
y
)
+
d
i
)
H_i=(H(key)+d_i)%m
Hi=(H(key)+di)
式中
H
(
k
e
y
)
H(key)
H(key)为散列函数;
i
=
0
,
1
,
2
,
.
.
.
,
k
(
k
≤
m
−
1
)
;
i=0,1,2,...,k(k≤m-1);
i=0,1,2,...,k(k≤m−1);m
表
示
散
列
表
表
长
;
表示散列表表长;
表示散列表表长;d_i$为增量序列。
取定某一增量序列后,对应的处理方法就是确定的。通常有以下4种取法.
线性探测法
当
d
i
=
0
,
1
,
2
,
.
.
.
,
m
−
1
d_i=0,1,2,...,m-1
di=0,1,2,...,m−1时,称为线性探测法。这种方法的特点是:冲突发生时,顺序查看表中下一个单元(探测到表尾地址m-1时,下一个探地址是表首地址0),直到找出一个空闲单元(当表未填满时一定能找到一个空闲单元)或查遍个表。
线性探测法可能使第i个散列地址的同义词存入第i+1个散列地址,这样本应存入第i+1个散列地址的元素就争夺第i+2个散列地址的元素的地址“……从而造成大量元素在相邻的散列地址上的聚集或堆积起来,大大降低了查找效率。
平方探测法
当
d
i
=
0
2
,
1
2
,
(
−
1
)
2
,
2
2
,
(
−
2
)
2
,
.
.
.
,
k
2
,
(
−
k
)
2
d_i=0^2,1^2,(-1)^2,2^2,(-2)^2,...,k^2,(-k)^2
di=02,12,(−1)2,22,(−2)2,...,k2,(−k)2时,称为平方探测法,其中
k
≤
m
/
2
k≤m/2
k≤m/2,散列
表长度m必须是一个可以表示成
4
k
+
3
4k+3
4k+3的素数,又称二次探测法。
平方探测法是一种较好的处理冲突的方法,可以避免出现“堆积”向题,它的缺点是不
能探到散列表上的所有单元,但至少能探测到一半单元。
再散列法
当
d
i
=
H
a
s
h
2
(
k
e
y
)
d_i=Hash_2(key)
di=Hash2(key)时,称为再散列法,又称双散列法。需要使用两个散列函数,当通过第一个散列函数H(key)得到的地址发生冲突时,则利用第二个散列函数Hash2(key)计算该关键字的地址增量。它的具体散列承数形式如下;
H
i
=
(
H
(
k
e
y
)
+
i
×
H
a
s
h
2
(
k
e
y
)
)
%
m
H_i=(H(key)+i×Hash_2(key))%m
Hi=(H(key)+i×Hash2(key))%m
初始探测位置
H
0
=
H
(
k
e
y
)
%
m
H_0=H(key)%m
H0=H(key)%m,i是冲突的次数,初始为0。在再散列法中,最多经过m一1次探测会遍历表中所有位置,回到
H
0
H_0
H0位置。
伪随机序列法
当
d
i
=
d_i=
di=伪随机数序列时,称为伪随机序列。
注意:在开放定址的情形下,不能随便物理除表中的已有元素,因为若删除元素,则会截断其他具有相同散列地址的元素的查找地址.因此,要删除一个元素时,可给它做一个删除标记,
进行逻辑删除。但这样做的副作用是:执行多次删除后,表面上看起来散列表很满,实际上有许多位置未利用,因此需要定期维护散列表,要把删除标记的元素物理删除。
拉链法
显然,对于不同的关键字可能会通过散列函敖映射到同一地址,为了避免非同义词发生冲
突,可以把所有的同义词存储在“个线性链表中,这个线性链表山其散列地址唯一标识。假设散列地址为i的同义词表的头指针存放在散列表的第个单元中,因而查找、插入和删除操作主要在同义词链中进行。拉链法适用于经常进行插入和删除的情况。
散列查找及性能分析
初始化:Addr=Hash(key);
1)检测查找表中地址为Addr的位置上是否有记录,若无记录,返回查找失败;若有记录,比较它与key的值,若相等,则返回查找成功,否则执行(2)。
2)用给定的处理冲突方法计算下一个散列地址,并把Addr置为此地址,转回步骤(1)。
对同一组关键字,设定相同的散列函数,则不同的处理冲突的方法得到的散列表不同,它们
的平均查找长度也不同,本例与上节采用拉链法的平均查找长度不同。
从散列表的查找过程可见:
(1)虽然散列表在关键字与记录的存储位置之间建立了直接映像,但由于“冲突”的产生,使
得散列表的查找过程仍然是一个给定值和关键字进行比较的过程。因此,仍需要以平均查找长度作为衡量散列表的查找效率的度量。
(2)散列表的查找效率取决于三不因素:散列函数、处理冲突的方法和装填因子。
装填因子。散列表的装填因子一般记为α,定义为一个表的装满程度,即
α
=
表
中
记
录
数
n
散
列
表
长
度
m
\alpha=\frac{表中记录数n}{散列表长度m}
α=散列表长度m表中记录数n
散列表的平均查找长度依赖于散列表的装填因子而不直接依赖于n或m。直观地看,α
越大,表示装填的记录越“满”,发生冲突的可能性越大,反之发生冲突的可能性越小。
读者应能在给出散列表的长度、元素个数及散列函数和解决冲突的方法后,在求出散列表的基
础上计算出查找成功时的平均查找长度和查找不成功的平均查找长度。
习题7.2
选择题
- 含有 n n n个表项的散列表,用线性探测法解决冲突,平均探测次数不超过β,则散列表应能够容纳 m m m项,满足 n m = 1 − 1 2 β − 1 \frac{n}{m}=1-\frac{1}{2\beta-1} mn=1−2β−11(Q6)
- K个关键词互为同义词,用线性探测法把K个关键字散列表,至少要进行 K ( K + 1 ) 2 \frac{K(K+1)}{2} 2K(K+1)
简答题
- 若要在散列表中删除一个记录,应如何操作?为什么?
如果是链表则直接物理删除;如果是开放定址法,只能做删除标记。改地址可能是该记录的同义词查找路径上的地址,如果物理地删除就中断了查找路径,因为查找时碰到空地址就认为是失败。
装载因子是用来计算散列表所有地址长度的。
8644




被折叠的 条评论
为什么被折叠?



