数据结构学习:查找

本文深入探讨了数据结构中的查找技术,包括顺序查找、折半查找、分块查找、B树和B+树的概念及操作。顺序查找适用于线性表,而折半查找在有序表中效率更高。分块查找结合了顺序和索引的优点。B树是一种多路平衡查找树,支持高效插入和删除操作。B+树进一步优化,所有数据存储在叶子节点,适合大量数据存储。此外,散列表提供直接关键字映射,但需处理冲突,常见的方法有拉链法和开放定址法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

对查找表的常⻅操作
①查找符合条件的数据元素
②插⼊、删除某个数据元素

查找算法的评价指标
查找⻓度——在查找运算中,需要对⽐关键字的次数称为查找⻓度
平均查找⻓度(ASL, Average Search Length )—— 所有查找过程中进⾏关键字的⽐较次数的平均值
在这里插入图片描述

一. 顺序查找

顺序查找,⼜叫“线性查找”,通常⽤于线性表。
算法思想:从头到尾挨个找(或者反过来也OK)
代码实现:

// 顺序查找
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++)
	{
		return i == SSTable.TableLen ? -1:i;//查找成功则返回元素下标,否则返回-1
	}
}

顺序查找的实现(哨兵)
将要查找的元素存到表中的第一个位置elem[0],然后从后往前查找

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

优点:⽆需判断是否越界,效率更⾼

在这里插入图片描述

二.折半查找

折半查找,⼜称“⼆分查找”,仅适⽤于有序的顺序表。

思路:
定义三个指针:
low=0
high=TableLen-1
mid=(low+high)/2(取整)
判断要查找的元素x和mid的大小关系
若x > mid,说明要找的元素在表的后半部分,则将low = mid + 1,并重新计算mid的位置并继续比较;
若x < mid,则将high = mid - 1,并重新计算mid的位置并继续比较.

// 折半查找
int Binary_Search(SSTable L,Elemtype key)
{
	int low = 0,high = ST.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
}

折半查找判定树的构造
如果当前low和high之间有奇数个元素,则 mid 分隔后,左右两部分元素个数相等
如果当前low和high之间有偶数个元素,则 mid 分隔后,左半部分⽐右半部分少⼀个元素
(右⼦树结点数-左⼦树结点数 = 0或1)
折半查找的判定树⼀定是平衡⼆叉树

折半查找的判定树中,只有最下⾯⼀层是不满的
因此,元素个数为n时树⾼ h = ⌈log2 (n + 1)⌉

判定树结点关键字:左<中<右,满⾜⼆叉排序树的定义
失败结点:n+1个(等于成功结点的空链域数量)

在这里插入图片描述

折半查找时间复杂度=O(log2 n)
顺序查找的时间复杂度=O(n)

三.分块查找

“索引表”中保存每个分块的最⼤关键字和分块的存储区间
特点:块内⽆序、块间有序

// 索引表
typedef struct
{
	Elemtype maxValue;
	int low,high;
}Index;

分块查找,⼜称索引顺序查找,算法过程如下:
①在索引表中确定待查记录所属的分块(可顺序、可折半)
②在块内顺序查找
在这里插入图片描述

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

在这里插入图片描述

四.B树

在这里插入图片描述

m叉查找树中,规定除了根节点外,任何结点⾄少有 个分叉,即⾄少含有 个关键字

B树,⼜称多路平衡查找树,B树中所有结点的孩⼦个数的最⼤值称为B树的阶,通常⽤m表示。⼀棵m阶B树或为空树,或为满⾜如下特性的m叉树:
1)树中每个结点⾄多有m棵⼦树,即⾄多含有m-1个关键字。
2)若根结点不是终端结点,则⾄少有两棵⼦树。
3)除根结点外的所有⾮叶结点⾄少有⌈m/2⌉ 棵⼦树,即⾄少含有⌈m/2⌉ -1个关键字。
在这里插入图片描述

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

m阶B树的核⼼特性:
1) 根节点的⼦树数∈[2, m],关键字数∈[1, m-1]。
其他结点的⼦树数∈[⌈m/2⌉ , m];关键字数∈[ -1, m-1]
2)对任⼀结点,其所有⼦树⾼度都相同
3)关键字的值:⼦树0<关键字1<⼦树1<关键字2<⼦树2<…. (类⽐⼆叉查找树 左<中<右)

1.B树的插入

在这里插入图片描述

插入元素60:
在这里插入图片描述

再插入元素80:
在这里插入图片描述

超出上限,故:
在这里插入图片描述

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

新元素⼀定是插⼊到最底层“终端节点”,⽤“查找”来确定插⼊位置
在这里插入图片描述

超出上限:

在这里插入图片描述在这里插入图片描述

插入时,插入到最下面,若满了,则取中间值向上传递扩展,若上面满了,再取中间值向上扩展

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

2.B树的删除

若被删除关键字在⾮终端节点,则⽤直接前驱或直接后继来替代被删除的关键字
直接前驱:当前关键字左侧指针所指⼦树中“最右下”的元素
直接后继:当前关键字右侧指针所指⼦树中“最左下”的元素

Eg:

在这里插入图片描述

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

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

在这里插入图片描述

删除90:

在这里插入图片描述

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

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

Eg:删除49后

在这里插入图片描述

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

在这里插入图片描述

五.B+树

⼀棵m阶的B+树需满⾜下列条件:
1)每个分⽀结点最多有m棵⼦树(孩⼦结点)。
2)⾮叶根结点⾄少有两棵⼦树,其他每个分⽀结点⾄少有⌈m/2⌉棵⼦树。(所有⼦树⾼度要相同)
3)结点的⼦树个数与关键字个数相等。
4)所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按⼤⼩顺序排列,并且相邻叶结点按⼤⼩顺序相互链接起来。
5)所有分⽀结点中仅包含它的各个⼦结点中关键字的最⼤值及指向其⼦结点的指针。
Eg:
在这里插入图片描述

B+树中,⽆论查找成功与否,最终⼀定都要⾛到最下⾯⼀层结点

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

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

六.散列表(Hash Table)

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

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

查找时:通过散列函数计算⽬标元素存储地址
查找⻓度——在查找运算中,需要对⽐关键字的次数称为查找⻓度
“冲突”越多,查找效率越低

最理想情况:散列查找时间复杂度可到达O(1)

装填因⼦α=表中记录数/散列表⻓度
装填因⼦会直接影响散列表的查找效率
eg:
在这里插入图片描述

0 1 2 4表示每个地址存的元素个数

常⻅的散列函数

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

2.直接定址法 —— H(key) = key 或 H(key) = a*key + b
a和b是常数。这种⽅法计算最简单,且不会产⽣冲突。它适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。

3.数字分析法 —— 选取数码分布较为均匀的若⼲位作为散列地址
设关键字是r进制数(如⼗进制数),⽽r个数码在各位上出现的频率不⼀定相同,可能在某些位上分布均匀⼀些,每种数码出现的机会均等;⽽在某些位上分布不均匀,只有某⼏种数码经常出现,此时可选取数码分布较为均匀的若⼲位作为散列地址。这种⽅法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。
Eg:
设计⻓度为10000的散列表,以⼿机号后四位作为散列地址

4.平⽅取中法——取关键字的平⽅值的中间⼏位作为散列地址。
具体取多少位要视实际情况⽽定。这种⽅法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布⽐较均匀,适⽤于关键字的每位取值都不够均匀或均⼩于散列地址所需的位数。

处理冲突的⽅法

1.拉链法

⽤拉链法(⼜称链接法、链地址法)处理“冲突”:把所有“同义词”存储在⼀个链表中
在这里插入图片描述

虽然取余相同,但是可以将取余相同的(同义词)存储再同一个链表中

2.开放定址法

所谓开放定址法,是指可存放新表项的空闲地址既向它的同义词表项开放,⼜向它的⾮同义词表项开放。其数学递推公式为:
Hi = (H(key) + di ) % m
i = 0, 1, 2,…, k(k≤m - 1),m表示散列表表⻓;di 为增量序列;i可理解为“第i次发⽣冲突”

开放定址法:
①.线性探测法
②.平⽅探测法
③.伪随机序列法

线性探测法—— di = 0, 1, 2, 3, …, m-1;即发⽣冲突时,每次往后探测相邻的下⼀个单元是否为空
冲突后重新计算地址,一般向后移一位,若为空则存放,不为空继续后移
Eg:
在这里插入图片描述

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

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

平⽅探测法:当di = 0^2 , 1^2 , -1^2 , 2^2 , -2^2 , …, k^2 , -k^2 时,称为平⽅探测法,⼜称⼆次探测法其中k≤m/2 (当冲突时不再一味的向后移了)

注意负数的模运算,(-3)%27 = 24,⽽不是3
《数论》中模运算的规则:a MOD m == (a+k*m) MOD m , 其中,k为任意整数

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

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

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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小二康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值