内容来源:王道考研——数据结构
顺序查找
- 又叫线性查找,主要用于线性表中查找
typedef struct {
ElemType *elem;
int TableLen ;
} SSTable ;
int search_seq (sstable st, ElemType key) {
//设置哨兵,不用对于最后是否查找成功进行判断;也可以使用判断而不是用哨兵
ST.elem [0]=key;
for (int i=ST.TableLen; ST.elem[i] ! =key; i- -);
return i;
}
-
平均查找长度:
A S L 成 功 = ∑ i = 1 n P i C i = ∑ i = 1 n P i ( n − i + 1 ) = ∑ i = 1 n 1 n ( n − i + 1 ) = n + 1 2 \mathrm{ASL}_{成功}=\sum_{i=1}^{n} P_{i} C_{i}=\sum_{i=1}^{n} P_{i}(n-i+1)=\sum_{i=1}^{n} \frac{1}{n} (n-i+1)=\frac{n+1}{2} ASL成功=∑i=1nPiCi=∑i=1nPi(n−i+1)=∑i=1nn1(n−i+1)=2n+1
A S L 失败 = ∑ i = 1 n P i C i = ∑ i = 1 n 1 n ( n + 1 ) = n + 1 \mathrm{ASL}_{\text {失败 }}=\sum_{i=1}^{n} \mathrm{P}_{i} \mathrm{C}_{\mathrm{i}}=\sum_{i=1}^{n} \frac{1}{\mathrm{n}}(\mathrm{n}+1)=\mathrm{n}+1 ASL失败 =∑i=1nPiCi=∑i=1nn1(n+1)=n+1
-
对于无序线性表,查找不存在的元素要查找所有元素;对关键字有序线性表进行顺序查找,查找失败时不一定要遍历整个线性表
-
有序表和无序表查找成功的平均查找长度相同
-
判定树:描述查找过程的二叉排序树
折半查找
- 又叫二分查找,只适用于有序的顺序表(使用数组进行存储)
int Binary_Search (SeqList L, ElemType key){
int low=0;
int high=L.TableLen-1;
int mid;
while (low<=high) { //注意这里必须是<=
mid =(low+high) / 2 ;
if (L.elem[mid]==key)
return mid;
else if (L.elem[mid]>key)
high=mid-1;
e1se
low=mid+1;
}
return -1;
}
-
对应的判定树更接近平衡二叉树
-
时间复杂度为 O ( l o g 2 ( n ) ) O(log_{2}(n)) O(log2(n))
-
顺序查找适用于顺序存储和链式存储,序列有序无序皆可;折半查找只适用于顺序存储,且要求序列一定有序。
分块查找
-
又称索引顺序查找,结合了顺序查找和折半查找的优点
-
对于数据进行分块,同一块内数据可以无序,不同块之间的元素有序,第i块内的元素都小于第i+1块内的元素;使用每一块中最大的数据元素代表该块;块内无序块间有序
-
确定块可以顺序查找或者折半查找,块内只能顺序查找
-
注意第一种的条件:均匀分块,块间块内都是用顺序查找
B树
基本内容
-
平衡二叉树上的查找不会退化为线性结构上的查找。B树是对平衡二叉树的改进,每个节点可以有多个关键字。
-
定义
-
B树示例
-
n个关键字, 阶数为m的B树高度为h满足
log m ( n + 1 ) ≤ h ≤ log ⌈ m / 2 ⌉ ( ( n + 1 ) / 2 ) + 1 \log _{m}(n+1) \leq h \leq \log _{\lceil m / 2\rceil }((n+1) / 2)+1 logm(n+1)≤h≤log⌈m/2⌉((n+1)/2)+1
-
叶子节点数对应查找失败的节点,叶子节点数等于所有关键字数目加一
查找
- 在B树中找节点——在磁盘中进行
- 在节点中找关键字——在内存中进行
插入
-
定位
查找插入该关键字的位置,即最底层中的某个非叶子结点 (规定一定是插入在最底层的某个非叶子结点内)
-
插入
- 不破会m阶B树的定义,即插入后结点关键字个数 在属于区间 [ ⌈ m / 2 ⌉ − 1 , m − 1 ] [ \lceil m/2\rceil-1, m-1] [⌈m/2⌉−1,m−1],则直接插入;
- 若插入后,关键字数量大于m-1,则对插入后的结点进行分裂操作
分裂
:
- 插入后的结点中间位置( ⌈ m / 2 ⌉ \lceil m/2\rceil ⌈m/2⌉)关键字并入父结点中, 中间结点左侧结点留在原先的结点中,右侧结点放入新的节点中;
- 若并入父节点后, 父结点关键字数量超出范围,继续想上分裂(向上分裂的目的是维持所有叶子节点都在最后一层),直到符合要求为止。
删除
- 终端节点:最底层非叶子节点非终端节点
- 除了终端节点和叶子节点之外的所有节点
对终端节点的删除操作
-
直接删除
若被删除关键字所在结点关键字总数> ⌈ m / 2 ⌉ \lceil m/2\rceil ⌈m/2⌉-1,表明删除后仍满足B树定义, 直接删除
-
兄弟够借
若被删除关键字所在结点关键字总数= ⌈ m / 2 ⌉ \lceil m/2\rceil ⌈m/2⌉-1,且与此结点邻近的兄弟结点的关键字个数> ⌈ m / 2 ⌉ \lceil m/2\rceil ⌈m/2⌉,则需要从兄弟结点借一个关键字,此过程需要调整该结点、双亲结点和兄弟结点的关键字
-
兄弟不够借
若被删除关键字所在结点关键字总数= ⌈ m / 2 ⌉ − 1 \lceil m/2 \rceil-1 ⌈m/2⌉−1, 且与此结点邻近的兄弟结点的关键字个数= ⌈ m / 2 ⌉ − 1 \lceil m/2 \rceil-1 ⌈m/2⌉−1, 则删除关键字, 并与一个不够借的兄弟结点和双亲结点中两兄弟子树中间的关键字合并。 合并后若双亲结点因减少一个结点导致不符合定义,则继续执行2、3步骤。
对非终端节点的删除操作
- 若小于k的子树中关键字个数> ⌈ m / 2 ⌉ − 1 \lceil m/2\rceil-1 ⌈m/2⌉−1,则找出k的前驱值k’, 并用k’ 来取代k, 再递归地删除k’即可。
- 若大于k的子树中关键字个数> ⌈ m / 2 ⌉ − 1 \lceil m/2\rceil-1 ⌈m/2⌉−1,则找出k的后继值k’, 并用k’ 来取代k, 再递归地删除k’即可。
- 若前后两子树关键字个数均为 ⌈ m / 2 ⌉ − 1 \lceil m/2\rceil-1 ⌈m/2⌉−1,则直接两个子结点合并, 然后删除k即可。
B+树
-
定义
-
示例
查找方式有两种:
- 从根节点开始向下查找的多路查找;
- 直接在叶子节点中的顺序查找
-
B树B+树对比
散列表
B树B+树基于比较进行查找;散列表建立了关键字与存储地址之间的直接映射关系,根据关键字而直接进行访问的数据结构。
- 散列函数:一个把查找表中的关键字映射成该关键字对应的地址(索引或者下标)的函数。
- 散列表存在的问题:冲突——把不同的关键字映射到相同的地址——关键在于设计好合适的散列函数
散列函数构造
散列函数的要求:
- 定义域必须包含全部需要存储的关键字,值域的范围则依赖于散列表的大小或地址范围。
- 散列函数计算出来的地址应该能等概率、均匀分布在整个地址空间中,从而减少冲突的发生。
- 散列函数应尽量简单, 能够在较短时间内计算出任一关键字对应的散列地址。
方法 | 定义 | 特点 |
---|---|---|
直接定址法 | 直接取关键字的某个线性函数值为散列地址 | 方法简单不产生冲突,如关键字分布不连续,则会造成空间浪费 |
除留取余法(最常用) | 关于p取余数,余数就是对应的地址 | 选好被除数p是关键,可以减少冲突。假定散列表表长为m,取一个不大于m但最接近或等于m的质数p |
数字分析法 | 取分布均匀分散的部分数字用于确定地址 | 适用于关键字已知的集合,若更换关键字则需要重新构造散列函数。 |
平方取中法 | 取关键字的平方值的中间几位作为散列地址 | 适用于关键字的每位取值不均匀或均小于散列地址所需要的位数。 |
折叠法 | 将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址 | 适用于关键字的位数多,而且关建字中的每位上数字分布大致均匀 |
冲突处理
开放定址法
是指可存放新表项的空闲地址既向它的同义词表项(散列函数计算结果相同)开放, 又向它的非同义词表项(散列函数计算结果不同)开放。
H i = ( H ( k e y ) + d i ) % m H_{i}=(H(key)+d_{i})\%m Hi=(H(key)+di)%m,m为散列表表长, d i d_{i} di为增量序列
增量序列确定方法:
-
线性探测法
对应位置不为空,从该位置开始一次向下寻找空闲位置。查找时需要比较对应位置是否是要找的关键字。可能产生
堆积现象
(关键字不出存在散列函数计算出的位置,通过寻找下一个空闲位置导致存入哈希表的数据在表中连成一片),会影响平均查找长度,降低查找效率 -
平方探测法
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,−12,22,−22....,k2,−k2,其中 k < = m / 2 k<=m/2 k<=m/2。可以避免堆积问题,缺点是不能探测到散列表上的所有单元(至少可以探测到一般单元)
-
再散列法
d i − i ∗ H a s h 2 ( k e y ) d_{i}-i*Hash2(key) di−i∗Hash2(key)
-
伪随机法
d i = 伪随机序列 d_{i}=\text{伪随机序列} di=伪随机序列
拉链法
在开放定址法不能随便删除元素(如果散列函数计算位置的值不是要查找的值,则不断向下比较寻找,如果位置关键字为空,认为查找失败,删除元素会造成出现空位置导致部分元素查找失败)
拉链法是指把所有同义词存放在一个线性链表中,这个线性链表由地址唯一标识,即散列表中每个单元存放该链表头指针。适用于经常用于插入删除的情况。
散列表查找
(1) 检测查找表中地址为Addr的位置上是否有记录, 若无记录, 则返回查找失败; 若有记录, 则比较它与key值, 若相等则返回成功, 否则执行步骤(2)
(2)包用给定的处理冲突方法计算 “下一散列地址” ,把Addr置为此地址, 转入步骤(1)
填装因子
表示表的装满程度: α = 表 中 记 录 数 n 散 列 表 长 度 m \alpha=\frac{表中记录数n}{散列表长度m} α=散列表长度m表中记录数n
散列表的平均查找长度依赖于散列表的填装因子