Chiriri's blog Chiriri's blog
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)

Iekr

苦逼后端开发
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)
  • 数据结构

  • 数据结构与算法

    • 绪论
    • 线性表
    • 栈和队列
    • 串
    • 树与二叉树
    • 图
    • 查找
      • 查找基本概念
        • 基本概念
        • 对查找表的常见操作
        • 查找算法的评价指标
      • 顺序查找
        • 顺序查找的算法思想
        • 顺序查找的实现(哨兵)
        • 查找效率分析
        • 顺序查找的优化(对有序表)
        • 用查找判定树分析ASL
        • 顺序查找的优化(被查概率不相等)
        • 总结
      • 折半查找
        • 算法思想
        • 代码实现
        • 查找效率分析
        • 折半查找判定树的构造
        • 折半查找的查找效率
        • 总结
      • 分块查找
        • 用折半查找查索引
        • 查找效率分析(ASL)
        • 总结
      • B树
        • 5叉查找树
        • 如何查找
        • 如何保证查找效率
        • B树
        • B树的高度
        • 总结
      • B树插入和删除
        • B树的插入
        • B树的删除
        • 终端节点-直接删除
        • 非终端节点-直接前驱/后驱
        • 被删除节点关键字个数低于下限-父子换位法
        • 被删除节点关键字个数低于下限-兄弟不够借合并
        • 总结
      • B+树
        • B+树的查找
        • B+树 VS B树
        • 总结
      • 散列查找
        • 散列表(Hash Table)
        • 处理冲突的方法——拉链法
        • 散列查找
        • 常见的散列函数
        • 处理冲突的方法——开放定址法
        • 处理冲突的方法——再散列法
        • 总结
    • 排序
  • 计算机组成原理

  • 操作系统

  • 408
  • 数据结构与算法
Iekr
2022-12-19
目录

查找

# 查找

# 查找基本概念

# 基本概念

  • 查找 —— 在数据集合中寻找满足某种条件的数据元素的过程称为查找
  • 查找表(查找结构)—— 用于查找的数据集合称为查找表,它由同⼀类型的数据元素(或记录)组成
  • 关键字 —— 数据元素中唯⼀标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯⼀的。

image-20221219001901080

image-20221219001916651

# 对查找表的常见操作

  1. 查找符合条件的数据元素
  2. 插入、删除某个数据元素

image-20221219001952966

image-20221219001959489

# 查找算法的评价指标

  • 查找长度 —— 在查找运算中,需要对比关键字的次数称为查找长度
  • 平均查找长度(ASL, Average Search Length )—— 所有查找过程中进行关键字的比较次数的平均值

通常认为查找任何⼀个元素的概率都相同

image-20221219002053438


评价⼀个查找算法的效率时,通常考虑查找成功 / 查找失败两种情况的 ASL

ASL 的数量级反应了查找算法时间复杂度

image-20221219002310322

image-20221219002330054

# 顺序查找

# 顺序查找的算法思想

顺序查找,又叫 “线性查找”,通常用于线性表。

算法思想:从头到尾挨个找(或者反过来也可以)

typedef struct { //查询表的数据结构(顺序表)
	ElemType *elem;//动态数组基址
	int TableLen; //表的长度
} SSTable;

//顺序查找
int Search_Seq(SSTable ST, ElemType key) {
	int i;
	for (i = 0; i < ST.TableLen && ST.elem[i] != key; ++i);
	//查找成功,则返回元素下标;查找失败,则返回-1
	return i == ST.TableLen ? -1;

}
1
2
3
4
5
6
7
8
9
10
11
12
13

image-20221219003039438

image-20221219003100417

# 顺序查找的实现(哨兵)

typedef struct { //查询表的数据结构(顺序表)
	ElemType *elem;//动态数组基址
	int TableLen; //表的长度
} SSTable;

//顺序查找
int Search_Seq(SSTable ST, ElemType key) {
	ST.elem[0] = key; //哨兵
	int i;
	for (i = ST.TableLen; ST.elem[i] != key; --i);
	//查找成功,则返回元素下标;查找失败,则返回0
	return i;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

image-20221219003349838

image-20221219003430827

优点:无需判断是否越界,效率更高

# 查找效率分析

成功失败ASL=∑i=1nPiCiASL成功 =1+2+3+…+nn=n+12 ASL 失败 =n+1

即O=(n)

# 顺序查找的优化(对有序表)

image-20221219003855894

image-20221219003927499

image-20221219003952492

image-20221219004028146

# 用查找判定树分析 ASL

image-20221219004059824

  • ⼀个成功结点的查找长度 = ⾃身所在层数
  • ⼀个失败结点的查找长度 = 其父节点所在层数
  • 默认情况下,各种失败情况或成功情况都等概率发⽣

# 顺序查找的优化(被查概率不相等)

image-20221219004235320

# 总结

image-20221219004259017

# 折半查找

折半查找,又称 “二分查找”,仅适用于有序的顺序表。

image-20221219004402537

33>mid,只可能在右边区域

# 算法思想

只有在 [low, high] 之间才有可能找到目标关键字

image-20221219004607675

image-20221219004708240

image-20221219004638416

image-20221219004652427

image-20221219004800706

image-20221219004752749


失败情况,当 low>high 时

image-20221219004925840

image-20221219004943215

# 代码实现

//折半查找
int Binary_Search(SSTable L, ElemType key) {
	int low = 0; high = L.TableLen - 1, mid;
	while (low <= high) {
		mid = (low + high) / 2; //取中间位置
		if (L.elem[mid] == key) {
			return mid; //查找成功则返回所在位置
		} else if (L.elem[mid] > key) {
			high = mid - 1; //从前半部分继续查找
		} else {
			low = mid + 1; //从后半部分继续查询
		}
	}
	return -1; //查找失败,返回-1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 查找效率分析

image-20221219060631820

image-20221219060649345

紫色为查找失败,绿色为查找成功

# 折半查找判定树的构造

  • 如果当前 low 和 high 之间有奇数个元素,则 mid 分隔后,左右两部分元素个数相等
  • 如果当前 low 和 high 之间有偶数个元素,则 mid 分隔后,左半部分比右半部分少⼀个元素
  • 折半查找的判定树中,若 mid =⌊( low + high )/2⌋ 则对于任何⼀个结点,必有:右子树结点数 - 左子树结点数 = 0 或 1

image-20221219061200105

判定树结点关键字:左 < 中 < 右,满⾜二叉排序树的定义

失败结点:n+1 个(等于成功结点的空链域数量)

# 折半查找的查找效率

image-20221219061331560

# 总结

image-20221219061351294

# 分块查找

image-20221219061753974

image-20221219061951364

image-20221219061959211

分块查找,又称索引顺序查找,算法过程如下:

  1. 在索引表中确定待查记录所属的分块(可顺序、可折半)
  2. 在块内顺序查找

# 用折半查找查索引

查找成功

image-20221219062139053

image-20221219062158004


查找失败

image-20221219062223841

image-20221219062231061

image-20221219062243859

image-20221219062259041

若索引表中不包含目标关键字,则折半查找索引表最终停在 low>high,要在 low 所指分块中查找

原因:最终 low 左边⼀定小于目标关键字,high 右边⼀定大于目标关键字。而分块存储的索引表中保存的是各个分块的最大关键字

image-20221219062517971

image-20221219062525319


查找失败

image-20221219062609520

image-20221219062619565

# 查找效率分析(ASL)

image-20221219062707563

image-20221219064846822

image-20221219064925984

若查找表是 “动态查找表”,使用链式存储方式可以更方便的插入删除

image-20221219065202592

# 总结

image-20221219064938554

# B 树

# 5 叉查找树

image-20221219065529852

# 如何查找

image-20221219065629237

image-20221219065651951

image-20221219065810983

# 如何保证查找效率

策略 1:m 叉查找树中,规定除了根节点外,任何结点至少有⌈m/2⌉ 个分叉,即至少含⌈m/2⌉−1 个关键字

image-20221219065947707

image-20221219070002414

策略 2:m 叉查找树中,规定对于任何⼀个结点,其所有子树的高度都要相同。

不够 “平衡”,树会很高,要查很多层结点

image-20221219070110811

# B 树

如果满足上述两个策略其实就是一个 5 阶 B 树

image-20221219071715842

image-20221219071751106

B 树,⼜称多路平衡查找树,B 树中所有结点的孩子个数的最大值称为 B 树的阶,通常用 m 表示。⼀棵 m 阶 B 树 或为空树,或为满⾜如下特性的 m 叉树:

  1. 树中每个结点至多有 m 棵子树,即至多含有 m-1 个关键字。

  2. 若根结点不是终端结点,则至少有两棵子树。

  3. 除了根节点外,任何结点至少有⌈m/2⌉ 个分叉,即至少含⌈m/2⌉−1 个关键字

  4. 所有非叶结点的结构如下: image-20221219072056364

    其中,()Ki(i=1,2,…,n)为结点的关键字,且满⾜;()K1<K2<…<Kn;Pi(i=0,1,…,n)为指向子树根结点 的指针,且指针Pi−1 所指子树中所有结点的关键字均小于Ki,Pi 所指子树中所有结点的关键字均大于Ki,n(⌈m/2⌉−1⩽n⩽m−1) 为结点中关键字的个数。

  5. 所有的叶结点都出现在同⼀层次上,并且不带信息(可以视为外部结点或类似于折半查找判定树的查找失败结点,实际上这些结点不存在,指向这些结点的指针为空)。

即 m 阶 B 树的核心特性:

  1. 根节点的子树数∈[2,m],关键字数∈[1,m−1]。 其他结点的子树数∈[⌈m/2⌉,m];关键字数∈[⌈m/2⌉−1,m−1]
  2. 对任⼀结点,其所有子树高度都相同
  3. 关键字的值:子树 0 <关键字 1 < 子树 1 < 关键字 2 < 子树 2<…. (类比二叉查找树 左 < 中 < 右)

# B 树的高度

含 n 个关键字的 m 阶 B 树,最小高度、最大高度是多少?

注:大部分学校算 B 树的高度不包括叶子结点(失败结点)

最小高度 —— 让每个结点尽可能的满,有m−1 个关键字,m 个分叉,则有

因此n⩽(m−1)(1+m+m2+m3+…+mh−1)=mh−1, 因此 h≥logm⁡(n+1)

最大高度 —— 让各层的分叉尽可能的少,即根节点只有 2 个分叉,其他结点只有⌈m/2⌉ 个分叉 各层结点至少有:第⼀层 1、第二层 2、第三层 2⌈m/2⌉… 第 h 层 2⌈m/2⌉h−2

第 h+1 层共有叶子结点(失败结点) 2⌈m/2⌉h−1 个

n 个关键字的 B 树必有 n+1 个叶子结点,则即n+1≥2(⌈m/2⌉)h−1, 即 h≤log⌈m/2⌉⁡n+12+1

# 总结

image-20221219073537234

# B 树插入和删除

# B 树的插入

5 阶 B 树 —— 结点关键字个数 ⌈m/2⌉−1≤n≤m−1 即:2≤n≤4 (注:此处省略失败结点)

image-20221219074546797

image-20221219074646595

在插⼊ key 后,若导致原结点关键字数超过上限,则从中间位置( ⌈m/2⌉)将其中的关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置(⌈m/2⌉ )的结点插⼊原结点的父结点

新元素⼀定是插⼊到最底层 “终端节点”,用 “查找” 来确定插⼊位置

如插入元素 90

image-20221219074819604

插入元素 99

image-20221219074916060

插入元素 88,右子树节点关键字数超过上限,找出⌈m/2⌉ 元素进行分裂,再将⌈m/2⌉ 的结点插入到原父节点中

image-20221219074940571

image-20221219075021627

插入元素 83

image-20221219075137051

插入元素 87

image-20221219075150950

插入元素 70

image-20221219075210294

此时我们⌈m/2⌉ 是元素 80,哪么元素 80 放置到哪个位置合适?

我们需要把 80 移动到它的原父结点中,但必须保证插入后的结点仍然保持有序性

image-20221219075411294

插入元素 93

image-20221219075516039

image-20221219075534454

插入元素 73,74,75 三元素

image-20221219075635661

image-20221219075704406

在插⼊ key 后,若导致原结点关键字数超过上限,则从中间位置($\lceil m / 2\rceil $)将其中的关键字分为两部分,左部分包 含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置( $\lceil m / 2\rceil $)的结点插⼊原结点的父结点。 若此时导致其父结点的关键字个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根结点为止,进 而导致 B 树⾼度增 1。

image-20221219075858282


核⼼要求:

  1. 对 m 阶 B 树 —— 除根节点外,结点关键字个数 ⌈m/2−1⌉≤n≤m−1
  2. 子树 0 < 关键字 1 < 子树 1 < 关键字 2 < 子树 2<….

新元素⼀定是插⼊到最底层 “终端节点”,用 “查找” 来确定插⼊位置

在插⼊ key 后,若导致原结点关键字数超过上限,则从中间位置($\lceil m / 2\rceil $)将其中的关键字分为两部分,左部分包 含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置( $\lceil m / 2\rceil $)的结点插⼊原结点的父结点。 若此时导致其父结点的关键字个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根结点为止,进 而导致 B 树⾼度增 1。

# B 树的删除

# 终端节点 - 直接删除

若被删除关键字在终端节点,则直接删除该关键字(要注意节点关键字个数是否低于下限⌈m/2⌉−1 )

如删除 60

image-20221219080241379

image-20221219080257641

# 非终端节点 - 直接前驱 / 后驱

若被删除关键字在非终端节点,则用直接前驱或直接后继来替代被删除的关键字,有两种方式 直接前驱:当前关键字左侧指针所指子树中 “最右下” 的元素

如删除 80

image-20221219080415655

image-20221219080506085

直接后继:当前关键字右侧指针所指子树中 “最左下” 的元素

如删除 77

image-20221219080610285

image-20221219080629895

若被删除关键字在非终端节点,则用直接前驱或直接后继来替代被删除的关键字

  • 直接前驱:当前关键字左侧指针所指子树中 “最右下” 的元素
  • 直接后继:当前关键字右侧指针所指子树中 “最左下” 的元素

对非终端结点关键字的删除,必然可以转化为对终端结点的删除操作

# 被删除节点关键字个数低于下限 - 父子换位法

若被删除关键字所在结点删除前的关键字个数低于下限,且与此结点右(或左)兄弟结点的关键字个数还很宽裕,则需要调整该结点、右(或左)兄弟结点及其双亲结点(父子换位法)

如删除 38

image-20221219080800333

image-20221219080821158

此时被删除的节点低于 m 阶 B 树的最小关键字个数,需要向它的右兄弟借,但不能直接换位

image-20221219081045902

image-20221219081129282

即当右兄弟很宽裕时,用当前结点的后继、后继的后继来填补空缺

我们来看借左兄弟的情况,如删除 90

image-20221219081500963

当左兄弟很宽裕时,用当前结点的前驱、前驱的前驱来填补空缺

image-20221219081555538

image-20221219081608734

本质:要永远保证 子树 0 < 关键字 1 < 子树 1 < 关键字 2 < 子树 2<….

# 被删除节点关键字个数低于下限 - 兄弟不够借合并

兄弟不够借。若被删除关键字所在结点删除前的关键字个数低于下限,且此时与该结点相邻的左、右兄弟结 点的关键字个数 $=\lceil m / 2\rceil $ ,则将关键字删除后与左(或右)兄弟结点及双亲结点中的关键字进行合并

image-20221219081745068

image-20221219081820987

image-20221219081850562

合并后,73 所在的节点也低于 m 阶 B 树的关键字结点个数的最小值,我们同时需要把 73 所在的节点进行合并

image-20221219082237366

image-20221219082257932

根节点为空,可以把根节点删除

image-20221219082340448

兄弟不够借。若被删除关键字所在结点删除前的关键字个数低于下限,且此时与该结点相邻的左、右兄弟结 点的关键字个数 $=\lceil m / 2\rceil $ ,则将关键字删除后与左(或右)兄弟结点及双亲结点中的关键字进行合并

在合并过程中,双亲结点中的关键字个数会减 1。若其双亲结点是根结点且关键字个数减少至 0(根结点关键 字个数为 1 时,有 2 棵子树),则直接将根结点删除,合并后的新结点成为根;若双亲结点不是根结点,且关 键字个数减少到⌈m/2⌉−2 ,则⼜要与它⾃⼰的兄弟结点进⾏调整或合并操作,并重复上述步骤,直至符合 B 树的要求为止。

# 总结

image-20221219085444347

# B + 树

image-20221219085642199

⼀棵 m 阶的 B + 树需满⾜下列条件:

  1. 每个分⽀结点最多有 m 棵子树(孩子结点)。
  2. 非叶根结点至少有两棵子树,其他每个分⽀结点至少有 $\lceil m / 2\rceil $ 棵子树。
  3. 结点的子树个数与关键字个数相等。
  4. 所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
  5. 所有分⽀结点中仅包含它的各个子结点中关键字的最大值及指向其子结点的指针。

# B + 树的查找

image-20221219090229809

image-20221219090320030

image-20221219090345750

image-20221219090408873

image-20221219090430855

image-20221219090439741

查询失败例子

image-20221219090511742

image-20221219090531270

image-20221219090547594

image-20221219090558151

image-20221219090604974

B + 树中,无论查找成功与否,最终⼀定都要走到最下面⼀层结点

也可以进行顺序查找,直接查找叶子结点

image-20221219090748255

image-20221219090759410

# B + 树 VS B 树

image-20221219091014562

image-20221219091134989

m 阶 B 树 / B + 树::

    • B + 树:结点中的 n 个关键字对应 n 棵子树
    • B 树:结点中的 n 个关键字对应 n+1 棵子树
    • B + 树:根节点的关键字数∈[1,m] 其他结点的关键字数∈[⌈m/2⌉,m]
    • B 树:根节点的关键字数∈[1,m−1]。 其他结点的关键字数∈[⌈m/2⌉−1,m−1]
    • B + 树:叶结点包含全部关键字,非叶结点中出现过的关键字也会出现在叶结点中
    • B 树:各结点中包含的关键字是不重复的
    • B + 树:叶结点包含信息,所有非叶结点仅起索引作用,非叶结点中的每个索引项只含有对应子 树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。
    • B 树:结点中都包含了关键字对应的记录的存储地址

B + 树典型应用:关系型数据库的 “索引”(如 MySQL)

在 B + 树中,非叶结点不含有该关键字对应记录的存储地址。 可以使⼀个磁盘块可以包含更多个关键字,使得 B + 树的阶更大,树⾼更矮,读磁盘次数更少,查找更快

# 总结

image-20221219091903641

# 散列查找

# 散列表(Hash Table)

散列表(Hash Table),⼜称哈希表。是⼀种数据结构,特点是:数据元素的关键字与其存储地址直接相关

通过 “散列函数(哈希函数)”: Addr=H(key)

例:有⼀堆数据元素,关键字分别为 {19, 14, 23, 1, 68, 20, 84, 27, 55, 11, 10, 79},散列函数 H(key)=key%13

image-20221219100832706

  • 若不同的关键字通过散列函数映射到同⼀个值,则称它们为 “同义词”
  • 通过散列函数确定的位置已经存放了其他元素,则称这种情况为 “冲突”

# 处理冲突的方法 —— 拉链法

  • 拉链法(⼜称链接法、链地址法)处理 “冲突”:把所有 “同义词” 存储在⼀个链表中

例:有⼀堆数据元素,关键字分别为 {19, 14, 23, 1, 68, 20, 84, 27, 55, 11, 10, 79},散列函数 H(key)=key%13

image-20221219101028995

# 散列查找

通过散列函数计算目标元素存储地址: Addr=H(Key)

如查找 27 的元素 27%13=1

image-20221219101139079

27 的查找长度 = 3


查找失败

如查找 21 的元素 21%13=8

image-20221219101258720

21 的查找长度 = 0,有的教材也会把 “空指针” 的判定算作⼀次比较 21 的查找长度 = 1,这里我们使用常用的长度为 0

  • 查找长度 —— 在查找运算中,需要对比关键字的次数称为查找长度

image-20221219101940824

image-20221219101949800

# 常见的散列函数

  • 除留余数法 —— H(key) = key % p 散列表表长为 m,取⼀个不大于 m 但最接近或等于 m 的质数 p

image-20221219102159370

为什么要取⼀个不大于 m 但最接近或等于 m 的质数 p?

image-20221219102308764

如果我们可能出现的关键字只有偶数

image-20221219102445281


  • 直接定址法 —— H(key) = key 或 H(key) = a*key + b

    其中,a 和 b 是常数。这种方法计算最简单,且不会产⽣冲突。它适合关键字的分布基本连续的情 况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。

image-20221219102621182


  • 数字分析法 —— 选取数码分布较为均匀的若⼲位作为散列地址

    设关键字是 r 进制数(如十进制数),而 r 个数码在各位上出现的频率不⼀定相同,可能在某些位 上分布均匀⼀些,每种数码出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出 现,此时可选取数码分布较为均匀的若⼲位作为散列地址。这种方法适合于已知的关键字集合, 若更换了关键字,则需要重新构造新的散列函数。

image-20221219102810927


  • 平方取中法 —— 取关键字的平方值的中间几位作为散列地址。

    具体取多少位要视实际情况而定。这种方法得到的散列地址与关键字的每位都有关系,因此使得 散列地址分布比较均匀,适用于关键字的每位取值都不够均匀或均小于散列地址所需的位数。

image-20221219103640321

image-20221219103707066

散列查找是典型的 “用空间换时间” 的算法,只要散列函数设计的合理,则散列表越长,冲突的概率越低。

# 处理冲突的方法 —— 开放定址法

所谓开放定址法,是指可存放新表项的空闲地址既向它的同义词表项开放,⼜向它的非同义词表项开 放。其数学递推公式为:Hi=(H(key)+di)%m

()i=0,1,2,…,k(k≤m−1),m 表示散列表表长;$d_i $ 为增量序列;i 可理解为 “第 i 次发⽣冲突”


  • 线性探测法 —— di=0,1,2,3,…,m−1;即发⽣冲突时,每次往后探测相邻的下⼀个单元是否为空

image-20221219104433143

image-20221219104403108

image-20221219104448656

image-20221219104556360

image-20221219104602418


查找操作

image-20221219104947463

image-20221219104953668

27 的查找长度 = 4

查找失败

image-20221219105040454

image-20221219105046619

image-20221219105110983

删除操作

image-20221219105225513

image-20221219105235356

image-20221219105326823

注意:采用 “开放定址法” 时,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填⼊ 散列表的同义词结点的查找路径,可以做⼀个 “删除标记”,进⾏逻辑删除


查找效率分析(ASL)

image-20221219105531429

image-20221219105549222

image-20221219105604379

线性探测法很容易造成同义词、非同义词的 “聚集(堆积)” 现象,严重影响查找效率 产⽣原因 —— 冲突后再探测⼀定是放在某个连续的位置


  • 平方探测法。当 $d^i = 0^2 , 1^2 , -1^2 , 2^2 , -2^2 , …, k^2 , -k^2 $ 时,称为平方探测法,⼜称二次探测法其中 k≤m/2

开放定址法:Hi=(H(key)+di)%m

()i=0,1,2,…,k(k≤m−1),m 表示散列表表长;$d_i $ 为增量序列;

image-20221219105907767

image-20221219110012532

注意负数的模运算,(−3)%27=24,而不是 3

《数论》中模运算的规则:aMODm==(a+km)MODm , 其中,k 为任意整数

非重点小坑:散列表长度 m 必须是⼀个可以表示成 4j + 3 的素数,才能探测到所有位置

image-20221219110253720


  • 伪随机序列法。di 是⼀个伪随机序列,如 di=0,5,24,11,…

开放定址法:Hi=(H(key)+di)%m

()i=0,1,2,…,k(k≤m−1),m 表示散列表表长;$d_i $ 为增量序列;

image-20221219214045361

image-20221219214101963

# 处理冲突的方法 —— 再散列法

再散列法(再哈希法):除了原始的散列函数 H (key) 之外,多准备几个散列函数,当散列函数冲突时,用下⼀个散列函数计算⼀个新地址,直到不冲突为止:

Hi=RHi(Key)i=1,2,3….,k

# 总结

image-20221219214335855

编辑 (opens new window)
上次更新: 2025/01/01, 10:09:39
图
排序

← 图 排序→

最近更新
01
k8s
06-06
02
进程与线程
03-04
03
计算机操作系统概述
02-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Iekr | Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式