B 树
- 概念
B 树,又称为 B-树或多路平衡查找树。 它是二叉平衡树 AVL 的一种改进,其所有结点平衡因子为 0, 本质上还是一颗查找树。 - B 树的阶
B 树中孩子结点数的最大值, 通常用 m 表示。 - B 树节点示例
![![[Pasted image 20251002155714.png]]](https://i-blog.csdnimg.cn/direct/9e73ea9f3d494e6dbe9e0b4330bc437e.png)
B 树的性质
- 一颗 m 阶的 B 树最多有 m 棵子树
- 从上图可以看出 points= keys + 1
- 多出一个 P0
#代表数量, 如 points 代表指针的数量
- root 节点是非叶子节点时, 满足如下条件
2≤#points≤m,1≤#keys≤m−1 2 \le \#points \le m, \qquad 1 \le \#keys\le m-1 2≤#points≤m,1≤#keys≤m−1 - 非 root 节点必须满足如下条件
[m2]≤#points≤m,[m2]−1≤#keys≤m−1 \left[ \frac{m}{2} \right] \le \#points \le m, \qquad \left[ \frac{m}{2} \right]-1 \le \#keys\le m-1 [2m]≤#points≤m,[2m]−1≤#keys≤m−1 - B 树所有叶节点位于同一层, 并且不带信息(空指针null)
B 树的高度
包含 n 个关键字 m 阶的 B 树最低高度:
hmin=[logm(n+1)]
h_{min} = [\log_{m}(n+1)]
hmin=[logm(n+1)]
包含 n 个关键字 m 阶的 B 树最高高度:
hmax=[log[m2](n+12)+1]
h_{max} = \left[ \log_{\left[ \frac{m}{2} \right]}\left( \frac{n+1}{2} \right)+1 \right]
hmax=[log[2m](2n+1)+1]
B 树的高度的推导
- 最低高度 hminh_{min}hmin
已知条件:
- 关键字的个数 n 是已经确定的。
- 为了让高度最小, 每一个节点应该容纳最多的关键字。
- 所有节点必须满足定义 keys≤ m-1。
综上, 每一个节点应当含有 m-1 个关键字, m 个指针
![![[Pasted image 20251002160404.png]]
![[Pasted image 20251003050859.png]]](https://i-blog.csdnimg.cn/direct/e462cfefb843448fad05fcc8f6c3cf2d.png)
因此得到递推关系:
4. level 1 有 m−1m - 1m−1 个关键字(1个结点)
5. level 2 有(m−1)×m(m - 1) × m(m−1)×m 个关键字(m个结点)
6. level 3 有(m−1)×m2(m - 1) × m^2(m−1)×m2 个关键字(m^2个结点)
7. …\dots…
8. level h 有(m−1)×mh−1(m - 1) × m^{h-1}(m−1)×mh−1 个关键字(mh−1m^{h-1}mh−1个结点)
9. level h+1 有(m−1)×mh(m - 1) × m^{h}(m−1)×mh 个关键字(mhm^{h}mh个结点)
假设最低高度为 h, 则第 h 层有(m−1)×mh−1(m - 1) × m^{h-1}(m−1)×mh−1个关键字(mh−1m^{h-1}mh−1个结点)
因此高度 h 层的 m 阶的 B 树第 h+1 层有 mhm^{h}mh个外部节点
因为含有 n 个关键字的查找树有 n+1 个外部结点
所以满足 n≤mh−1n≤m^{h }-1n≤mh−1 即 h≥logm(n+1)h ≥ log_{m}(n + 1)h≥logm(n+1)
- 最高高度 hmaxh_{max}hmax
已知条件:
-
关键字的个数 n 是已经确定的。
-
为了让高度最大, 每一个节点应该容纳最少的关键字。
-
树是一棵查找树, 查找树会有 n+1 个查找失败的位置。
-
root 结点, 需要满足#keys≥2−1=1\#keys \ge 2-1 = 1#keys≥2−1=1
![![[Pasted image 20251002161344.png]]](https://i-blog.csdnimg.cn/direct/672b2d8f8d1e4dedb64053a52b984188.png)
-
非 root 节点需要满足#keys≥[m2]−1\#keys \ge \left[ \frac{m}{2} \right]- 1#keys≥[2m]−1
![![[Pasted image 20251002161523.png]]](https://i-blog.csdnimg.cn/direct/61fc66f9438f4d4f98df9ed1843dc01f.png)
![![[Pasted image 20251003051251.png]]](https://i-blog.csdnimg.cn/direct/110f84682adf4e219087edd4d61d0fa0.png)
综上:
root 结点有两个指针, 1 个关键字;
非 root 结点有 [m2]\left[ \frac{m}{2} \right][2m]个指针, [m2]−1\left[ \frac{m}{2} \right]-1[2m]−1个关键字
因此得到递推关系:
level1有1个结点
level2有2个结点
level3有2×[m2]2 \times \left[ \frac{m}{2} \right]2×[2m]个结点
level4有2×[m2]×[m2]=2×[m2]22 \times \left[ \frac{m}{2} \right]\times \left[ \frac{m}{2} \right]=2 \times \left[ \frac{m}{2} \right]^{2}2×[2m]×[2m]=2×[2m]2个结点
…\dots…
level h有2×[m2]h−22 \times \left[ \frac{m}{2} \right]^{h-2}2×[2m]h−2个结点
level h+1 有2×[m2]h−12 \times \left[ \frac{m}{2} \right]^{h-1}2×[2m]h−1个外部结点
第h+1层的m阶的B树共有结点数nh+1=2×[m2]h−1n_{h+1}=2 \times \left[ \frac{m}{2} \right]^{h-1}nh+1=2×[2m]h−1
2×[m2]h−1−1≤n2 \times \left[ \frac{m}{2} \right]^{h-1}-1 \le n2×[2m]h−1−1≤n
h≤1+log[m2](n+12)h \le 1+\log_{\left[ \frac{m}{2} \right]}\left( \frac{n+1}{2} \right)h≤1+log[2m](2n+1)
hmax=[1+log[m2](n+12)]h_{max}=\left[ 1+\log_{\left[ \frac{m}{2} \right]}\left( \frac{n+1}{2} \right) \right]hmax=[1+log[2m](2n+1)]
B 树的查找
步骤:
- 利用 b 树的性质找到所查关键字所在的结点
- 在结点内进行顺序查找或折半查找
![![[Pasted image 20251002204948.png]]](https://i-blog.csdnimg.cn/direct/7fc7cd30b2134b45a67fb1ae13272619.png)
B 树的插入
步骤:
- 利用 b 树的查找算法, 先找到该关键字应该插入的最低结点位置(一定是最底层非叶子结点), 查找失败的位置就是插入位置。
- 插入该关键字后判断当前结点是否依旧满足 m 阶 b 树的定义(关键字个数要求), 若大于最大关键字个数, 则要对当前结点进行分裂操作。
分裂操作: 将当前不满足要求的结点分为三部分, 左, 中间元素, 右,然后构造一个新结点, 将左部分关键字保留在原结点, 将右部分关键字存入新的结点内, 将中间位置的一个元素存入其双亲结点内。 若此时双亲结点也需要进行分裂操作, 则重复上述操作即可, 直至根结点。
B 树的删除
步骤:
- 若被删除关键字 k 位于非终端结点, 可以用其前驱或后继 m 替代 k, 然后在相应位置删除原来的 m, 而且 m 必定是终端结点, 则此问题转化为在终端结点删除关键字。
- 若被删除的关键字位于终端结点(最底层非叶子结点):
- 若被删除关键字所在结点关键字个数≥ceil(m/2), 则直接删除关键字。
- 若被删除关键字所在的结点关键字个数=ceil(m/2)-1,且与此结点相邻的左或右兄弟结点关键字个数≥ceil(m/2), 则需要调整左兄弟结点+该结点+双亲结点(或该结点+双亲结点+右兄弟结点)的结构以达到新的平衡,如图1。
- 若被删除关键字所在的结点关键字个数=ceil(m/2)-1,且与此结点相邻的左或右兄弟结点关键字个数都=ceil(m/2)-1, 则需要将删除后的结点与双亲结点以及左(或右)兄弟结点的关键字进行合并操作,如图2。若合并后双亲结点是根结点且关键字个数减少至 0, 则合并后的新结点成为根结点; 若双亲结点不是根结点, 则需要与它自己的兄弟以及双亲结点进行合并操作, 直至当前的 b 树符合要求。
![![[Pasted image 20251002205259.png]]](https://i-blog.csdnimg.cn/direct/74c44a13548e4a9f98ddc0225eeb7032.png)
B+树
- B+树的阶
B+树中孩子结点数的最大值, 通常用 m 表示 - B+树节点示意图
![![[Pasted image 20251002205603.png]]](https://i-blog.csdnimg.cn/direct/fa7141c761ef440fb3ca187339561f89.png)
B+树的性质
- 一颗 m 阶的 B 树最多有 m 棵子树
- 从上图可以看出#points=#keys\#points= \#keys#points=#keys
- root 节点是非叶子节点时, 满足如下条件2≤#points≤m2 ≤ \#points ≤ m2≤#points≤m
- 非 root 节点必须满足如下条件[m2]≤#points≤m\left[ \frac{m}{2} \right]≤ \#points≤ m[2m]≤#points≤m
- B+树叶节点包含了所有的关键字, 叶节点则使用指针连起来组成链表
- 非叶节点与其子节点的关系相当于索引查找的结构
B 与 B+树的区别
![![[Pasted image 20251002210032.png]]](https://i-blog.csdnimg.cn/direct/46b13cb953864a178e4e5e6ec97ed8fe.png)
B+树示例
![![[Pasted image 20251003051632.png]]](https://i-blog.csdnimg.cn/direct/33bae08bce7a4cc19b23290393a32fe1.png)
Hash 表
基本概念
- 定义
Hash 查找又称散列查找, 是一种通过计算数据元素的存储地址进行查找的一种方法。 - 典型应用
Python 中 dict 的实现、 C ++标准库(STL) 中 hashtable 的实现、 MD5 值等。 - 散列函数
将关键字映射为地址的函数, 记为 Hash(key)=addressHash(key)=addressHash(key)=address - Hash 冲突
由于散列函数不一定是单射函数,因此存在 Hashkey1=Hashkey2Hash key1=Hash key2Hashkey1=Hashkey2 , 此时我们称之发生了 Hash 冲突。 - 删除
在对Hash表进行删除的时候,我们并不做真正的删除操作,只是做一个标记表明这个元素已经被删除了,当下次再Hash到该位置时,再进行覆盖。
Hash 函数的构造
- 直接定址法
HASH(key)=a×key+b HASH(key)=a \times key+b HASH(key)=a×key+b
- a 和 b 是任意实数
- 由于线性函数是一个单射函数, 不存在 Hash 冲突
- 适用于关键字分布连续的情况, 关键字分布不连续会浪费大量空间
- 除留余数法
HASH(key)=key%p HASH(key)=key\%p HASH(key)=key%p
- 除留余数法由于 mod 函数不是单射函数, 因此会产生 Hash 冲突。我们后面所说的 Hash 冲突一般都是以除留余数法为 Hash 函数
- 该方法的关键是选择 p 以减少冲突, p 一般取不大于表长的最大素数
- 注意表长 m 和 p 没有直接的关系
- 数字分析法
- 平方取中法
Hash 冲突处理方法
开放定址法:散列地址计算公式:Hi=(H(key)+di)%mH_{i} = (H(key)+d_{i})\%mHi=(H(key)+di)%m
• 线性探测法(di为线性变化)
• 平方探测法(di为平方变化)
• 再散列法 (di为hash函数变化)
• 伪随机序列 (di为随机变化)
注:开放定址法下的删除操作只能是逻辑删除,不能物理删除
开放定址法——线性探测法
- 线性探测过程
当 Hash(key)产生冲突时, 一次探测 Hash(key) + 1、 Hash(key) + 2、Hash(key) + 3 等位置, 直到找到一个空闲位置或者回到 Hash(key)位置(超出表长则回到表起点) 。 - 缺点——堆积现象
由于线性探测法会使得 Hash(key) =i 的元素争夺 i + 1 号位置, 当 i + 1 号位置被占领时, 开始争夺 i+ 2、 i + 3 等位置, 使得原本应当在第 i 号位置的元素大 量聚集在 i 号位置附近, 造成“堆积”现象, 降低了查找的效率。
原因:开放定址法的特点是可以存放新元素的空闲地址既向它的同义词开放,也向它的非同义词开放。
例1,将{26, 36, 41, 38, 44, 14, 68, 12, 6, 51, 25}插入散列表中,使用散列函数Hash(key)=key%11,表长m=13,冲突解决采用线性探测法
![![[Pasted image 20251003052215.png]]](https://i-blog.csdnimg.cn/direct/38d5cdb790f54296995d00bc78637ce4.png)
Hash(26)=26%11=4
Hash(36)=36%11=3
Hash(41)=41%11=8
Hash(38)=38%11=5
Hash(44)=44%11=0
Hash(14)=14%11=3
Hash(68)=68%11=2
Hash(12)=12%11=1
Hash(6)=6%11=6
Hash(51)=51%11=7
Hash(25)=25%11=3
- Hash查找的效率(ASL分析)
查找元素26, Hash(26) =4,需要查找1次
查找元素36, Hash(36) =3,需要查找1次
查找元素41, Hash(41) =8,需要查找1次
查找元素38, Hash(38) =5,需要查找1次
查找元素44, Hash(44) =0,需要查找1次
查找元素14, Hash(14) =3,需要比较3、4、5、6位置,查找4次
查找元素68, Hash(68)=2,需要查找1次
查找元素12, Hash(12) =1,需要查找1次
查找元素6, Hash(6)=6,需要比较6、7位置,查找2次
查找元素51, Hash(51)=7,需要比较7、8、9位置,查找3次
查找元素25, Hash(25)=3,需要比较3、4、5、6、7、8、9、10位置,查找8次
ASL成功=1∗7+4+2+3+811=2411 ASL_{成功}=\frac{1*7+4+2+3+8}{11}= \frac{24}{11} ASL成功=111∗7+4+2+3+8=1124
对于每一个被查找的元素,只有找到空位置时才可以确定查找失败。
对于0号位置,需要查找到11号空位才可结束,共需比较12次
对于1号位置,共需比较11次
对于2号位置,共需比较10次
对于3号位置,共需比较9次
对于4号位置,共需比较8次
对于5号位置,共需比较7次
对于6号位置,共需比较6次
对于7号位置,共需比较5次
对于8号位置,共需比较4次
对于9号位置,共需比较3次
对于10号位置,共需比较2次
散列函数Hash(key)=key%11不可能算出11以后的任何位置
ASL失败=12+11+⋯+3+211=7 ASL_{失败}=\frac{12+11+\dots+3+2}{11}=7 ASL失败=1112+11+⋯+3+2=7
开放定址法——平方探测法
- 平方探测过程
- 当产生 Hash(key)产生冲突时, 依次探 测 Hash(key)+ 1^2 、Hash(key)-1^2 、 Hash(key)+2^2、 Hash(key)- 2^2等
- 最远可以探测到 Hash(key)+k^2、 Hash(key)-k^2位置
- k≤m/2, 而 m 必须是一个可以表达为 4k+3 的素数
- 直到找到一个空闲位置或者又回到 Hash(key)位置
- 性质
平方探测法避免了堆积问题, 但它无法探测到表上所有的位置, 只能保证探测到一半的位置
例2,将{26, 36, 41, 38, 44, 14, 68, 12, 6, 51, 25}插入散列表中,使用散列函数
Hash(key)=key%11,表长m=13,冲突解决采用平方探测法
![![[Pasted image 20251003052437.png]]](https://i-blog.csdnimg.cn/direct/8c88d71e556a46daa00abf9a4e9fef12.png)
Hash(26)=26%11=4
Hash(36)=36%11=3
Hash(41)=41%11=8
Hash(38)=38%11=5
Hash(44)=44%11=0
Hash(14)=14%11=3
Hash(68)=68%11=2
Hash(12)=12%11=1
Hash(6)=6%11=6
Hash(51)=51%11=7
开放定址法——再散列法
- 再散列法过程
当产生Hash(key)产生冲突时,使用另一个HashHashHash函数Hash2Hash_{2}Hash2计算增量
add=(Hash(key)+i×Hash2(key))%m add =(Hash(key) +i \times Hash_{2}(key)) \% m add=(Hash(key)+i×Hash2(key))%m
其中i为冲突次数,m为表长 - 性质
再散列法中最多经过 m-1 次就会回到 Hash(key)的位置
将{26, 36, 41, 38, 44, 14, 68, 12, 6, 51, 25}插入散列表中,使用散列函数
Hash(key)=key%11,表长m=13,冲突解决采用再探测法,Hash2(key)=key%7
![![[Pasted image 20251003052540.png]]](https://i-blog.csdnimg.cn/direct/2ea4e84a5054489f83e7c12318656b22.png)
Hash(26)=26%11=4
Hash(36)=36%11=3
Hash(41)=41%11=8
Hash(38)=38%11=5
Hash(44)=44%11=0
Hash(14)=14%11=3
add=(Hash(key)+i∗Hash2(key))
add=(Hash(key)+i*Hash_{2}(key))%m
add=(Hash(key)+i∗Hash2(key))
拉链法
- 定义
当发生 Hash 冲突时, 将所有关键字都存在 Hash(key)所在的位置,存储格式使用链表形式 - 结构图
![![[Pasted image 20251002211001.png]]](https://i-blog.csdnimg.cn/direct/8f6be2509644461981b1086593b8376b.png)
例3. 将{26, 36, 41, 38, 44, 14, 68, 12, 6, 51, 25}插入散列表中,使用散列函数
Hash(key)=key%11,表长m=13,冲突解决采用拉链法
![![[Pasted image 20251003052803.png]]](https://i-blog.csdnimg.cn/direct/31c5143b72c0478096da9b0383eaee3c.png)
ASL成功=1∗9+2∗1+3∗111=1411
ASL_{成功}=\frac{1*9+2*1+3*1}{11}= \frac{14}{11}
ASL成功=111∗9+2∗1+3∗1=1114
ASL失败=2∗8+4∗1+1∗211=2
ASL_{失败}=\frac{2*8+4*1+1*2}{11}=2
ASL失败=112∗8+4∗1+1∗2=2
装填因子α
- 定义
表示散列表的装满程度,一般记为α\alphaα
α=nm \alpha = \frac{n}{m} α=mn
其中n为表中装入的关键词数量,m为表长
若题中给出装填因子和关键词数量,则表长为
m=[nα] m = \left[ \frac{n}{\alpha} \right] m=[αn] - 性质
直观上看,ααα越大代表散列表装填的越满,发生冲突的可能性就越大;反之发生冲突的可能性则越小
HASH 总结
- Hash查找由于Hash冲突的发生,仍然需要进行关键字的比较,因此仍然以ASL作为效率评估的指标。
- Hash查找成功的ASL需要以关键字作为切入点。
- Hash查找失败的ASL需要以位置作为切入点,每一个位置需要探测到离它最近的空位为止。
- Hash 查找失败时, 请注意对于 HASH(key)=key%pHASH(key) = key \% pHASH(key)=key%p 而言 , 需要分析到 p-1 号位置, p 号以后的位置永远不会再失败时被分析到。
- Hash 查找的效率之与散列函数、 处理冲突的方法和装填因子有关。
2797

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



