数据结构 - 检索
1. 检索算法的分类
- 顺序表和线性表方法
- 散列法(根据关键码直接访问)
- 树索引法
2. 自组织线性表
根据估算的访问频率排列记录
2.1 启发式规则
- 计数方法(类似LFU):按照到现在为止实际出现的记录访问频率顺序保存记录
- 移至前端方法(类似LRU,最近最少方法):找到一条记录就把它放到线性表的最前面
- 转置:将找到的记录与他在线性表中的前m条记录交换位置
2.2 应用场景:需要频繁插入记录的检索
3. 二分查找
时间复杂度:O( log n )
4. BST(二叉检索树)和AVL树(平衡二叉树)
已排序的普通二叉树特点:检索快( O( log(n) )),插入慢(O( n) )
4.1 BST(二叉检索树)
4.1.1 特点
- 左子树小于根,右子树大于根
- 中序遍历:从小到大输出
- 检索插入删除均快
- 时间复杂度总体来说是O( h )
- 树平衡时是O( log(n) )
- 退化成线性时是O( n )
4.1.2 插入流程
- 从根开始,比较关键码大小(若为空树,成为根)
- 一路向下比较,直到成为叶子
4.1.3 查找流程
- 从根开始,比较关键码大小(若为空树,返回null)
- 一路向下比较,直到找到对应关键码所在的节点,或是找到空子树(返回null)
4.1.4 删除流程(删除一个特定节点)
- 从根开始,比较关键码大小(若为空树,返回null)
- 一路向下比较,直到找到对应关键码所在的节点,或是找到空子树(返回null)
(即:查找流程)- 将要删除的节点移出树,同时将树的上下部分连接
如果要删除的节点: - 是叶子:则用null代替它
- 有左右子树:用
左子树中的最大值(/右子树中的最小值)代替该节点,然后删除左子树中的最大值(/右子树中的最小值)(?) - 只有左子树或者右子树:将左右子树的根替代要删除的节点的位置
- 将要删除的节点移出树,同时将树的上下部分连接
4.2 AVL树(平衡二叉树)
4.2.1 特点
任一节点的左右子树高度差最多为1
※:检索的时间复杂度:O( log( n ))
4.2.2 建立方法与操作
4.2.2.1 建立方法
4.2.2.2 操作
- 插入
- 删除
5. 散列法(hashing)
5.1 定义
散列:通过计算,将关键码值映射到数组中的位置来访问记录的过程
散列表:存放记录的数组,HT
散列函数:把关键码映射到位置的函数,h
槽:散列表中的一个位置,0~M-1
冲突:散列函数将两个关键码散在了同一个槽中
冲突解决策略
※散列目标:HT[ h(K) ] = K
,即通过散列函数计算得到的散列表中的位置存放的关键码与要检索的关键码相同
5.2 散列函数
- h(x) = x%C
- 平方取中法:用于数值,有效利用数的每一位。
h(x) = (x2/C)%D
取中间r位,2r = D,D是数的位数 - 折叠法:用于字符串
- 将所有字母的ASCII表值累加,然后对其以散列表长取模
- 字符串中字母顺序对散列函数无影响
循环移位法:更好的方法
5.3 冲突解决策略
5.3.1 开散列(单链方法)
- 定长数组中每个元素为一个篮子(bucket),存放指向链表的指针
篮子中的记录排列顺序任意 - 插入、检索、删除时间复杂度:O(1+n/D)
5.3.2 闭散列(开地址法)
放置时:
- 所有记录直接放置在哈希表里
- 冲突解决策略来决定发生冲突时怎么放
故而查找时:
用相同的冲突解决策略(如散列函数)来找到对应位置
5.3.2.1 桶式散列
- 哈希表中有B个篮子,每个篮子里固定M个槽
篮子里的每个槽可以是装关键码经散列函数后相同的,也可以装不同的 - 散列方法放置记录,直到占满对应桶中的每个槽
- 放不下的记录统统放进一个溢出桶(使其较小)中
5.3.2.2 线性探查(最常用的散列方法)※👌
使用散列表中的任何一个槽
5.3.2.2.1 定义
探查序列
5.3.2.2.2 实现
// Insert e into hash table HT
template <typename Key, typename E>
void hashdict <Key, E>::
hashInsert (const Key& k,const E& e) {
int home;
// Home position for e
intpos=home=h(k);
//Init probe sequence
for (int i=1; EMPTYKEY != (HT[pos]) .key(); i++) {
pos =(home + p(k,i)) % M; // probe
Assert(k != (HT[pos]).key(),"Duplicates not allowed" ) ;
KVpair<Key, E> temp(k, e) ;
HT[pos] = temp;
}
5.3.2.2.3 探查函数:p(k,i)
避免聚集问题
1. 聚集的定义和类别
两个关键码的探查序列链绞在一起
- 基本聚集:关键码的探查序列某些段重叠在一起
- 二级聚集:探查函数若只是槽的函数而不是关键码的函数,同时散列函数在某个基槽聚集,那伪随机探查和二次探查仍然会保持聚集的问题 -> 解决:利用初始关键码的信息,使用第二个散列函数
2. 探查函数种类
- 最简单:p(k,i) = i
可能导致基本聚集 - p(k,i) = c*i
c与M互素 - 伪随机探查:p(k,i) = Perm[ i - 1 ]
- 所有插入和检索都使用相同的“随机”数序列
- Perm:长度为M-1的数组,包含从1~M-1间的数的随机排列
- 二次探查:**p(k,i) = c1i2 + c2i + c3
- 二次函数作为探查函数
- 最简单的变体:p(k,i) = i2
- 可能的问题:散列表中有些值不在探查序列里
- 双散列方法:p(k,i) = i * h2( k )
- 避免二级聚集