目录
直接插入排序(straight insertion sort)
❼查找结构
查找表: 需要被查的元素的集合
关键字/键值(key):数据元素中某个数据项的值
- 若此key可以唯一标识一个记录,则为主关键字(primary key),所在数据项称为主关键码
- 若此key可识别多个记录,则为次关键字(secondary key),所在数据项称为次关键码
查找表分类
静态查找表(static search table):只做查找操作
- 查询某“特定”数据元素是否在查找表中
- 检索某“特定”数据元素和各种属性
动态查找表(dynamic search table):查找同时可以插入或删除
- 查找时插入(不存在)数据元素
- 查找时删除(已存在)数据元素
查找的存储结构
- 静态查找表:线性表结构存储;顺序查找算法(再对主key排序可应用折半查找)
- 动态查找表:二叉排序树
- 散列表结构
顺序查找表
- 设置哨兵(
),循环判断下标是否越界(
)
- 时间复杂度:
缺:效率低下,所以应用于小型数据查找
有序表查找
(1)折半查找/二分查找(binary search):线性表必须采取顺序存储
- 代码:
- 时间复杂度:
多用于静态查找表,用于动态查找表时,由于频繁插入或删除,维护有序的排序需要不小的工作量
(2)插值查找(interpolation search):要查找的key与查找表中最大最小记录的key比较后查找
- 插值公式:
- 代码:
- 时间复杂度:
对于表长较长,且key分布较均匀的查找表,插值查找好于二分查找;反之,
这种分布不均匀的,插值查找并不合适。
(3)斐波那契查找(fibonacci search)
:查找成功
: 新范围是第low~第mid-1,范围个数为F(k-1)-1个
: 新范围是第m+1~第high,范围个数为F(k-2)-1个
- 代码:
- 时间复杂度:
平均性能优于二分查找,但最坏情况,低于二分查找
线性索引查找
关于索引:
(1)稠密索引:将数据集中的每个记录对应一个索引项
索引项有序,证明查找可使用二分、插值、斐波那契查找,大大提高了效率;但如果数据集非常大,索引项与数据集记录个数相同,查找性能反而下降。
(2)分块索引:每块对应一个索引项
- 块需要满足的需求:块内无序、块间有序
- 时间复杂度:介于
与
之间。
索引项结构的三个数据项:
- 最大关键码
- 存储了块中的记录个数,以便于循环使用
- 用于指向块首数据元素的指针,便于开始对这一块中记录进行遍历
查找步骤
- 在分块索引表中查找要查关键字所在的块(块间有序,所以二分、插值等算法查找)
- 根据块首指针找到相应块,并在块中顺序查找关键码(块内无序,只能顺序查找)
(3)倒排索引:记录号表存具有相同次key的所有记录的记录号(指向记录的指针/记录主key)
- 最基础的搜索技术
- 每项:属性值+属性值的各记录的地址
- 不是由记录确定属性值,而是属性值来确认记录位置,故为倒排
索引项通用结构
- 次关键码:例如下图“英语单词”
- 记录号表:例如下图“文章编号”
二叉排序树(binary sort tree):提高查找和插入删除key的速度
- 空树
- 若左子树不空:左子树上所有结点值均小于它的根结构的值
- 若右子树不空:右子树上所有结点值均大于它的根结构的值
- 左右子树分别为二叉排序树
二叉排序树插入
- 将关键字放到树中的合适位置
二叉排序树删除
- 叶子结点直接删除
- 仅有左/右子树的结点:独子承父业
- 左右子树都有的结点:找到结点的直接前驱/直接后继,来替换删除结点(如下图)
二叉排序树的存储结构:链表(插入删除仅需修改链接指针)
比较平衡的二叉排序树的时间复杂度:
斜树的时间复杂度:
二叉平衡树(AVL树):每节点左右子树高度差至多为1的二叉排序树
(平衡因子)BF:二叉树结点的左子树深度减去右子树深度的值
AVL树的BF只能为:-1、0、1
最小不平衡子树:距离插入结点最近的,且BF绝对值大于1的结点为根的子树
构建AVL树
- BF值为正:整棵树右旋(顺时针旋转)
- BF值为负:整棵树左旋(逆时针旋转)
- 最小不平衡子树的BF与它的子树的BF符号相反时:先旋一次使符号相同,再反向旋一次
查找、插入、删除时间复杂度:
比较理想的一种动态查找表算法
多路查找树(B树):每结点可有两个以上孩子,且每个结点处可存储多个元素
- 2-3树和2-3-4树都是B树的特例
- 结点最大的孩子数目为B树的阶(order):2-3树为3阶B树
在B树上查找是一个顺指针查找结点和结点中查找关键字的交叉过程
2-3树:每结点都有2/3个孩子(2结点/3结点),且所有叶子都在同一层次上
2-3树的插入
- 空树:直接插入一个2结点
- 将结点插入到2结点的叶子上:将其升级为3结点
- 将结点插入到3结点上:拆分3结点,再从树中两元素或插入元素三者中选择其一向上移动一层
2-3树的删除
(1)结点位于3结点的叶子结点上:直接删除
(2)结点位于2结点上:删除一个只有一个元素的结点
- 此结点双亲也是2结点,且拥有一个3结点的右孩子:旋转
- 此结点双亲是2结点,它的右孩子也是2结点:整棵树变形,再旋转
- 此结点双亲是3结点:结点拆分再合并
- 若当前树为满二叉树:减少层数
(3)结点位于非叶子的分支结点:中序遍历得到前驱或后继,再补位
- 删除的分支节点为2结点
- 删除的分支节点为3结点的某一元素
2-3-4树:4结点包含小中大三个元素和四个孩子(或没有孩子)
- 4结点要么没有孩子,要么4个孩子
B+树:应文件系统所需而出的一种B树的变形树
- 插入删除同B树:只不过操作元素都在叶子结点上进行
随机查找与B树相同,但若从最小关键字进行从小到大的顺序查找,B+树很有优势;B+树结构特别适合带有范围查找,比如查我们学校18~22学生人数,从根节点找到第一个18岁学生,然后在叶子结点按顺序查找到符合范围的所有记录。
散列表查找(哈希表)
散列技术
- 通过key查找时不用比较就可获得记录存储位置
- 是一种存储方法,也是一种查找方法(散列是面向查找的存储结构)
- 记录间不存在逻辑关系,只与key有关联
- 最适合的求解问题是查找与给定值相等的记录
- 冲突(collision):
却有
,则
和
为同义词
同key对应很多记录:不适用散列技术;只有如用班级学生的学号/身份证号来散列存储,此时一个号码唯一对应一个学生才比较合适
散列表也不适合范围查找:如查找18~22岁同学;无法获得表中记录的排序;无法计算最大值、最小值等结果
散列函数的构造方法
直接定址法:
- 取key的某个线性函数值为散列地址
- 需要事先知道key的分布情况,适合查找表较小且连续的情况
数字分析法:例如选择号码后四位称为散列地址
- 抽取:使用key的一部分来计算散列存储位置的方法
- 避免抽取出现冲突:抽取数字反转、右环位移、左环位移、叠加等
- 常用于key位数比较大的情况,若事先知道key分布且key的若干位分布均匀,使用数字分析法
平方取中法:
- 适合不知道key分布,且key位数不是很大的情况
折叠法:
- 将key从左到右分割成位数相等的几部分,并叠加求和,按表长取后几位为散列地址
- 适合不知道key分布,且key位数很大的情况
除留余数法:
- mod取模(求余数)
- 最常用
随机数法:
- 适用于key长度不等的情况
处理散列表冲突
堆积:本不是同义词的两个key争夺一个地址
开放定址法:一旦冲突就寻找下一个空的散列地址,只要散列表够大总有空散列地址存记录
- 线性探测法
- 二次探测法:增加平方运算,不让key都聚集在某一区域
- 随机探测法:随机函数计算得到位移量
再散列函数法:冲突就换一个散列函数计算
链地址法:同义词key存在一个单链表中(同义词子表),散列表中只存所有同义词子表的头指针
公共溢出区法:为所有冲突的key建立一个公共的溢出区来存放
❽排序:使序列成为一个按key有序的序列
内排序与外排序
内排序:插入排序、交换排序、选择排序、归并排序
冒泡排序(bubble sort)
- 让每个key都与其后的每个key比较,如果大则交换,这样第一位置的key在一次循环后一定变成最小值
- 改进冒泡算法:增加一个标记变量flag来判断此序列是否已经有序
- 时间复杂度:
简单选择排序(simple selection sort):
- 顺序扫描序列中的元素,先找出最小的,再找出第二小的(从除了上一次找出来的元素,也就是剩下的元素中找出最小的),直至排序完成
- 时间复杂度:
直接插入排序(straight insertion sort)
- 将数组划分为有序和无序部分,将无序中记录依次插入到有序部分的合适位置上,得到一个新的、记录数加1的有序表
- 时间复杂度:
- 三者均为
,但直接插入 优于 简单选择 优于 冒泡排序
希尔排序(shell sort)
- 直接插入排序的改进版,也被称为缩小增量排序
- 将相距某个增量的记录组成一个子序列(分组),对每组直接插入排序(排序)。随着增量逐渐减少,每组包含的key越来越多,当增量减至1时,整个文件恰被分成一组,算法终止。
- 不稳定
- 时间复杂度:
堆排序(heap sort)
堆是具有以下性质的完全二叉树:
- 大顶堆:每个结点的值都大于等于其左右孩子结点的值
- 小顶堆:每个结点的值都大于等于其左右孩子结点的值
堆结构:将大/小顶堆层序遍历存入数组
- 简单选择排序的一种改进
- 构建堆的时间复杂度为
,重建堆为
,堆排序时间复杂度为
- 不稳定
- 不适合待排序序列较少的情况
归并排序(merging sort)
- 时间复杂度:
- 递归实现归并排序
- 非递归实现归并序列
快速排序(quick sort)
是任取待排序序列的一个元素作为中心元素(可以用第一个,最后一个,也可以是中间任何一个),习惯将其称为pivot,枢轴元素;
将所有比枢轴元素小的放在其左边;
将所有比它大的放在其右边;
形成左右两个子表;
然后对左右两个子表再按照前面的算法进行排序,直到每个子表的元素只剩下一个。
- 冒泡排序的升级
- 时间复杂度:
- 不稳定
快速排序的优化
优化选取枢轴:
- 三数取中法(去三key先排,将中间数作为枢轴,一般取左右端、中间三个数)
- 九数取中法
优化不必要的交换
优化小数组时的排序方案
优化递归操作:尾递归优化
关于其他数据结构的总结:上一篇笔记