目录
7.1 查找的相关概念
查找的基本概念:一般情况下,被查找的对象称为查找表(线性表、树表和哈希表)。
查找表包含一组元素(或记录),每个元素由若干个数据项组成,并假设有能唯一标识元素的数据项,称为主关键字(默认按主关键字查找)。
查找基于的数据模型是集合、
线性表:适用于静态查找,顺序查找、折半查找等技术
树表:适用于动态查找,二叉排序树的查找技术
散列表:静态查找和动态查找均适用,采用散列技术
- 静态查找表是只作查找操作的查找表,主要操作有查询某个“特定的”数据元素是否在查找表中,检索某个“特定的”数据元素及其属性。
- 动态查找表是在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。
- 若整个查找过程都在内存进行,则称之为内查找。
- 反之,若查找过程中需要访问外存,则称之为外查找。
查找算法中的主要操作是关键字之间的比较,所以通常把查找过程中关键字平均比较次数也就是平均查找长度作为衡量一个查找算法效率优劣的依据。
7.2 线性表的查找
三种线性表查找方法,即顺序查找、折半查找和分块查找算法。
7.2.1 顺序查找(Linear Search)
思路:
- 从顺序表的一端开始依次遍历,将遍历的元素关键字和给定值k相比较,
- 若两者相等,则查找成功,返回该元素的序号。
- 若遍历结束后,仍未找到关键字等于k的元素,则查找失败,返回-1。
- 默认从顺序表的前端开始遍历。
原本顺序查找每次都要判断index是否小于总长。改进:将待查值放在查找方向的尽头,就避免了每次判断是否越界。
时间复杂度:O(n)
7.2.2 折半查找(Binary Search)
思路:数组排好序,选择中间数(low+high)/2作为基准,判断查找值与中间数的大小关系,来判断查找值在哪个区间。将区间逐渐细化直到low>=high
折半查找时间复杂度:
判定树(折半查找判定树):描述折半查找判定过程的二叉树
- 折半查找任一记录的过程,即是判定树中从根结点到该记录结点的路径。比较次数等于该记录结点在树中的层数
- 当n不等于
时(h表示树的高度),其折半查找判定树不一定为满二叉树,但可以证明n个结点的判定树的高度与n个结点的完全二叉树的高度相等,即h为
或者
。
内部结点是mid的元素值(如果没给就用下标)
外部节点表示查找不成功时元素对应的范围
(因为外部结点比较次数 = 外部结点层数-1)
折半查找判定树和二叉排序树的区别:折半查找判定树是基于有序集合的,二叉排序树是基于无序集合的
7.3 树表的查找
7.31 二叉排序树
几种特殊树形结构统称为树表。这里树表采用链式存储结构,属于动态查找表。
1. 二叉排序树(BST)的定义:
二叉排序树或者是空树,或者是满足如下性质的二叉树:
(1) 若左子树非空,则左子树上所有结点值(默认为结点关键字)均小于根结点值
(2) 若右子树非空,则右子树上所有结点值均大于根结点值
(3) 左、右子树本身又各是一棵二叉排序树
特点:中序序列是一个递增有序序列
2. 二叉排序树的构建:
定义二叉排序树的结点类:有四个属性:key关键字,data数据项,lchild左孩子指针,rchild右孩子指针
定义二叉排序树类:有两个属性:r根节点,f待删除结点的双亲结点(删除操作时用)
3. 二叉排序树的插入:
在根节点r的二叉排序树中插入关键字为k的结点的过程:
若r为空,创建一个key=k的结点,将它作为根结点
若k<r.key,将k插入r结点的左子树
若k>r.key,将k插入r结点的右子树
若k=r.key,说明树中已有关键字k
4. 二叉排序树的生成:
先创建根节点,之后每用上面插入的方法逐个将关键字k插入到二叉排序树中。
5. 二叉排序树的查找:
查找定值k的过程:
若r是空树,则查找失败
若k=r.key,则查找成功
若k<r.key,则在r的左子树上查找
若k>r.key,则在r的右子树上查找
算法上用递归查找
查找中关键字比较次数不超过树的高度
6. 二叉排序树的删除:
删除通过修改双亲的相关指针实现
删除结点p的几种情况:
若p是叶子结点:直接删除,将双亲结点中相应指针域的值改为空
若p只有左孩子:将双亲结点中相应指针域指向p的左子树
若p只有右孩子:将双亲结点中相应指针域指向p的右子树
若p有两个孩子:将双亲结点中相应指针域指向p的左子树中关键字最大的结点
7. 时间复杂度:
平均情况:O(n)~O(log2n)
最好情况:相当于折半查找
最坏情况:相当于线性查找
关键字序列不同,二叉排序树也不同,查找性能不同。高度最小的二叉排序树性能为O(log2n),高度最大的二叉排序树性能为O(n)
插入、删除、查找的时间复杂度相同
(因为外部结点比较次数 = 外部结点层数-1)
【例题】按照序列(50,38,30,45,40,48,70,60,75,80)构造一棵二叉排序树
= 2.9
= 3.55
7.32 平衡二叉树
- 平衡二叉树定义:或者是一棵空的二叉排序树,或者是具有下列性质的二叉排序树
根结点的左子树和右子树的深度最多相差1
根结点的左子树和右子树也都是平衡二叉树
- 平衡因子:该结点的左子树的深度减去右子树的深度。在平衡二叉树中,结点的平衡因子是1、0或-1.(叶子结点平衡因子为0,其余往上算)
- 最小不平衡子树:以距离插入结点最近的、且平衡因子的绝对值大于1的结点为根的子树
- 含有n个结点的AVL树对应的查找时间复杂度为O(log2n)
推荐视频:【AVL树的定义 .调整 .插入操作】如何理解AVL树这个数据结构_哔哩哔哩_bilibili
1. 调整平衡二叉树:如果一次插入后,树不平衡,就调整最小不平衡子树,不影响其他结点。
调整分为LL型、RR型、LR型、RL型
2. 构造AVL树:每插入一次就判断是否平衡,如果不平衡就调整
【例题】
3. 删除AVL树结点:
删除结点x方法:
如果x左子树为空,用右孩子替换它
如果x右子树为空,用左孩子替换它
如果x同时有左右子树:
如果左子树较高,用左子树中最大结点q的值替换x的值,删除q结点
如果右子树较高,用右子树中最小结点q的值替换x的值,删除q结点
如果x没有左右子树,直接删除。
以上哪一步中出现不平衡就调整
【例题】
7.4 哈希表查找
哈希表(Hash Table)又称散列表的基本概念:
设要存储的元素个数为n,设置一个长度为m (m>=n)的连续内存单元
以每个元素的关键字ki(0≤i≤n-1)为自变量,通过一个哈希函数h把ki映射为内存单元的地址(或相对地址)h(ki)。并把该元素存储在这个内存单元中。
- 对于两个不同的关键字ki和kj(i≠j)出现h(ki)=h(kj),这种现象称为哈希冲突。
- 将具有不同关键字而具有相同哈希地址的元素称为“同义词”,这种冲突也称为同义词冲突。
7.4.1 哈希函数构造方法:
1. 直接定址法
以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。即h(k)=k+c。
优点:这种哈希函数计算简单,并且不可能有冲突发生。
缺点:若关键字分布不连续将造成内存单元的大量浪费。
2. 数字分析法
提取关键字中取值较均匀的数字位作为哈希地址的方法。
适合于所有关键字值都已知的情况,并需要对关键字中每一位的取值分布情况进行分析。
3. 除留余数法
用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。
除留余数法的哈希函数h(k)为:h(k)=k mod p (mod为求余运算,p≤m)
p的选取:不大于m且接近m的素数。
p≤m 来保证地址有效
p为质数 来保证冲突尽可能小
发生哈希冲突有关的三个因素:
1)与装填因子有关。所谓装填因子α是指哈希表中已存入的元素数n与哈希地址空间大小m的比值,即α=n/m。
α越小,冲突的可能性就越小; 但α越小,存储空间的利用率就越低。
2)与所采用的哈希函数有关。
3)与解决冲突的哈希冲突函数有关。
7.4.2 解决哈希冲突的方法
解决哈希冲突可分为开放定址法和拉链法两大类
1 开放定址法
发生冲突时查找周围一个空位置存放记录。
设置一个查找周围一个空位置的函数。
(1)线性探测法
- 从发生冲突的地址(设为d0 )开始,依次循环探测d的下一个地址(当到达下标为m-1的哈希表表尾时,下一个探测的地址是表首地址0),直到找到一个空闲单元为止。
- 描述公式为:
哈希函数值不相同的多个记录争夺同一个后继哈希地址称为非同义词冲突, 导致出现堆积。
(2)平方探测法
- 发生冲突时前后查找空位置。
- 描述公式为:
平方探测法可以避免出现堆积问题。
缺点是不能探测到哈希表上的所有单元,但至少能探测到一半单元。
【例题】
2. 拉链法
- 拉链法是把所有的同义词用单链表链接起来的方法。
- 在这种方法中,哈希表每个单元中存放的不再是记录本身,而是相应同义词单链表的头指针。
- 由于单链表中可插入任意多个结点,所以此时装填因子α根据同义词的多少既可以设定为大于1,也可以设定为小于或等于1,通常取α=1。
- 插入时插入到头指针后一个位置
哈希表查找及性能分析:
1. 采用开放定址法建立的哈希表的查找:在哈希表内顺序查找
2. 采用拉链法建立的哈希表的查找:在对应下标的链表中顺序查找