【考研—数据结构】查找合集

注:以下内容均为个人复习使用,不保证所述内容绝对严谨;
注:考研知识点相对基础,因此这里只做知识点合集,不保证内容详细。

Pre:

查找表、关键字、查找(检索)
静态查找:不更改查找表,仅进行访问操作
动态查找:支持插入、删除、访问

顺序表查找、链表查找、散列表查找、索引查找(B+树,B-树)

一、查找的性能分析:

ASL平均查找长度 = Σ Pi * Ci (查找频率*查找次数)
ASL成功= 总比较次数 / 元素个数 (当元素在数据结构中存在时考虑)
ASL失败= 总比较次数 / 失败情形个数 (当元素在数据结构中不存在时考虑)

二、静态查找算法(顺序查找、二分查找)

1. 顺序查找:

从一端到另一端寻找。(遍历)
ASL =(n+1)/2
单词查找的时间复杂度 O(n)

2. 折半查找:

有关定义

即:二分查找。
根据二分查找划出的查找流程图,称为 二叉搜索树 ,又称判定树。
每次查询搜索深度不超过log n 。 二分查找一定没有回溯。
默认边界向下取整严格区间 , 即 mid=(l+r)/2, dfs(mid+1,r)与dfs(l,mid-1)。

使用前提

顺序表存储结构、存储序列有序排列

时间复杂度 与 ASL

单次查询的 时间复杂度O(logn)
limit n->+∞ 时, ASL = height - 1
ASL成功 n为具体数字时,暴力计算;注:若 n = 2h-1, 即为满二叉,也可以直接用结论, ASL= height - 1
ASL失败 n为具体数字时,暴力计算;注:额外画出表示查询失败的外延节点,题目如果说假定概率相同,即指到达每个外延节点出现的概率相同。
失败的外部节点的个数 = n+1 。 【逻辑推证:每两个数字之间、序列两端,各有一个失败情况,所以是n+1。不需要再从二叉树节点个数计算】

Code (二分)

递归写法
int Q(int l,int r,int va){//区间[l,r]查找va
	if(l>r) return false;//查询失败
	int mid=(l+r)>>1;
	if(va==a[mid]) return mid;
	else if(va>a[mid]) return Q(mid+1,r,va);
	else return Q(l,mid-1,va);
非递归写法
int Q(int va){
	int l=1,r=n;
	while(l<=r){
		int mid=(l+r)>>1;
		if(va<a[mid]) l=mid+1;
		else if(va<a[mid]) r=mid-1;
		else return mid;
	}return false;//查询失败
}

3. 分块查找

有关定义

即块间有序,使用二分查找法寻找所属块;块内不一定有序,使用顺序查找法查找块内位置。
假定块大小为 k, 则 实际时间复杂度 为 O( k + log2(n/k) ),其中k为常数。(考研考试写的时候记得把常数抹了)
考古一个高中学的分块:思想理解:中和时间和空间(定期重构、分块)

分块大小的选择,根号n

为了最大程度的折中时空复杂度,最佳的选择为 每块大小:k = (√n),块数:k = (√n)

时间复杂度 与 ASL

单次查询的时间复杂度为 O( (√n) + log2(√n) )
ASL = 【(k+1) / 2 】+【log2(k+1)】。 (即:块内顺序的ASL为 (k+1)/2, 块间二分的ASL为 log2k)

三、散列查找(Hash哈希、杂凑)

1. 术语 :

Hash函数:就是映射,令存储位置=Hash ( key ) 。
同义词:存在不同的 key 导致 Hash( key ) 相同,这些key称为同义词。
装填因子:a= n/len , 总关键字个数除以表长。

2. 哈希函数好坏的评判

可操作性 : 操作简单、易于实现
冲突较小: Hash查找的ASL与时间复杂度 均为 O(冲突深度) 或 O(平均冲突次数),所以冲突越小越好
散列均匀: 即 在对哈希冲突进行处理的时候,要尽可能减少堆积现象,保证散列分布相对均匀。

理解堆积
eg. 如果采用线性探测法解决冲突,我们会发现存在这样一种情况:我们是通过占用其他位置的方法解决冲突,这样就会导致让其他原本没有冲突碰撞的元素反而因此发生碰撞,从而增加了冲突次数/平均冲突深度。

3. 哈希函数的构造

直接定址法: H(key)= a* key + b
数字分析法:取关键字的若干位 或 组合 作为地址
平方取中法:将关键字平方后 取中间几位 作为地址
折叠法: 将关键字分割成几段,将几段的重合部分作为地址
推荐使用:
除留余数法构造Hash函数:
取p = 不大于表长的最大质数
Hash(key) = key % p

4. 哈希冲突的解决

(1) 开放定址法(探测空位法)

①线性探测法:从初次发生冲突的位置,依次向后探测(循环列表遍历一圈)寻找空位置。
优点:只要表未满,总能找到不冲突的散列地址;
缺点:每次冲突都是在冲突最近的空地址放置,带来新的冲突的可能,并且冲突“聚集”。
②二次探测法(平方探测法):从初次发生冲突的位置,依次探测 [pos ± i2]的位置,(即每次+1,-1,+4,-4,+9,-9…).
优点:避免冲突聚集。
缺点:跨度太大

注1:该哈希冲突解决方法下,如果要从哈希表中进行某个元素删除,需要采用的是标记法。
原因是:如果直接进行物理删除,则有可能出现,查找通路连通性被损坏的情况。

查找失败的标志:全表被搜索过 or 搜索到空位置。

(2) 链地址法:(链表存冲突)

冲突发生时,直接以该节点为头创建链表,将新key放入链表。
开散列: 处理冲突的方式为 将冲突元素放置hash表外的空间
闭散列: 处理冲突的方式为 将冲突元素在hash表内的空间寻找位置
查找失败的标志: 链表被搜索完依然没找到。

(3) 再哈希法

冲突发生时,令H[i]=ReHash( H[i] )

(4) 公共溢出区

另外开一个数组,冲突的元素都扔这里

5. Hash的ASL计算(平均查找长度)

ASL成功:按照hash函数查找,查到即成功
ASL失败:按照hash函数查找,遇到空位置即失败

6. 时间复杂度

哈希查找的时间复杂度,与节点个数n无关,只与 负载因子 有关,即冲突次数。

7. 装填因子 / 负载因子 α

装填因子/负载因子 = (元素个数) / (哈希表长)

四、动态查找算法(平衡树)

BST树 和 AVL树
二叉排序树 用树表示序列,左子树节点均小于root,右子树节点均大于root
下文中,查询、插入、删除、平衡,均为O( 树深度 ),平均复杂度为O(log n)。
注:下文中代码均为盲写,只用于帮助我自己理解自己的文字内容,不保证运行。
附:SBT平衡树模板链接

1> BST树 Binary Search Tree 二叉查找树

1. 查询操作:
查询值为va的点,每次判断va与root的关系,如果va<root则进入左子树,否则进入右子树

inline int query(int va,int root){
	if(root==0) return -1;//无该数字
	if(va==root) return root;
	if(va<root) return query(va,root.lchild);
	if(va>root) return query(va,root.rchild);
}

2. 插入操作:
插入一个值为va的点,每次判断va与root的关系,如果va<root则进入左子树,否则进入右子树
直到所要进入的左子树/右子树不存在,则直接将该点作为左子树/右子树
如果值为va的点已经存在,则不进行插入
3. 修改操作:
=查询操作+更改值(不改树形)
4. 删除操作:
本质思路:类比线性序列,当我们删除一个点时,应当将比他小的最大数or比他大的最小数拿来替代自己的位置。
如果所删除点没有左右子树,则直接删除
如果所删除点只有左子树/只有右子树,则直接用左子树/右子树代替它的位置
如果所删除点既有左子树又有右子树,则用中序直接前驱/后继 代替它的位置,于是问题转化为,删除中序直接前驱/后继。(可以推证的是,中序直接前驱/后继,一定是没有其右子树/左子树)。所以两步实现。
在这里插入图片描述
注:这是一种时间执行效率较高的树上删除的方法

inline void erase(int va){
	int pos=query(va);
	int alter_pos= L_link[pos];//中序前置
	tree[pos].va=tree[alter_pos].va;//自己变祖先
	fa[alter_pos].r_child = alter_pos.lchild;//孩子给祖先
}

5. 建树操作:
=对空树进行插入操作。

2>平衡二叉树 AVL

pre: 一个二叉查找树的查询效率与树形深度有关,O(log depth)
目的:通过一些实时维护的操作,以保证每次查询时间相对稳定。
二叉树性质:总点数相同时,树形越接近完全二叉树,则深度越小。
维护操作:时刻保持左右子树的深度差值不超过1。

分类讨论需要维护操作的情况:

  1. 可能导致不平衡的情况:
    插入操作之后: 自己所在子树的高度变高。(根据前文,插入操作只会将 被插入点 作为 叶子节点)
    删除操作之后: 自己所在子树的兄弟子树变高。
    注:该节点所在子树指,包含该节点的所有子树。因此插入删除操作之后,需要将该节点的所有祖先节点都进行维护。
  2. 不平衡的种类有一下四种可能:
    在这里插入图片描述
    (a) LL形 (b) LR形 © RR形 (d) RL形
  3. 平衡操作:
    - LL形调整:
    对于root点,其右子树中有节点被删除or其左子树中被插入了新元素,则可能出现LL形。
    在这里插入图片描述
    浅证一下:
    如图所示变形。其中L、root、fa表示点,其余已经平衡后的子树。
    分析可知,先前 depth[L] = max( depth[LL], depth[LR] ) +1 = depth[R] +2, 其中LL,LR,R的深度没有发生改变
    根据平衡性质可知,LL与LR的深度相差 <= 1,R与LR或LL的深度差1
    ⇒ 当前depth[root]= max(depth[LR],depth[R]) +1= depth[LR]+1 <= depth[LL]+2
    ⇒ depth[LL]>=depth[LR]情况下,该操作恒成立。
    LL形的含义就是 depth[LL] >= depth[LR]
    会带来depth[LL]<=depth[LR]的情况,我们称之为LR形
#if 0
思路:
root->Lchild 作为 root
root 及其右子树  整体作为 root->Lchild 的右子树
root->Lchild 的右子树被挤掉,root的左子树空缺 => 把r->L->R 送给r
#endif

inline void LL_reverse(int root){
	int LL=root->Lchild;
	root->Lchild = LL->Rchild;// 右儿子给root当左儿子
	//root->Rchild 依然为 root->Rchild
	LL->Rchild=root;//root变儿子
	if (Fa[root]->Rchild == root) Fa[root]->Rchild = LL;//左儿子继位
	else (Fa[root]->Lchild == root) Fa[root]->Lchild = LL;//左儿子继位
}

- LR形调整
在这里插入图片描述

#if 0
思路:
root->Lchild->Rchild 作为 root
root 及其右子树  整体作为 root->Lchild->Rchild 的右子树
root-Lchild及其左子树  整体作为root->Lchild->Rchild 的左子树
root->Lchild->Rchild 的右子树被挤掉,root的左子树空缺 => 把root->LR->R 送给root当Lchid
root->Lchild->Rchild 的左子树被挤掉,root->Lchild的右子树空缺 => 把root->LR->L 送给root->Lchild当Rchild
#endif

inline void LR_reverse(int root){
	int LR=root->Lchild->Rchild,L=root->Lchild;
	L->Rchild = LR->Lchild //左儿子给自己的父亲当右儿子
	root->Lchild = LR->Rchild;// 右儿子给root当左儿子
	//root->Rchild 依然为 root->RchildR
	LR->Rchild=root;//root变右儿子
	LR->Lchild=L;//L做左儿子
	if (Fa[root]->Rchild == root) Fa[root]->Rchild = LR;//LR儿子继位
	else (Fa[root]->Lchild == root) Fa[root]->Lchild = LR;//LR儿子继位
}

- RR形调整
类似LL调整, 根据对称性,Ctrl CV之后, 把LL调整中的左右全部互换就可以了
(如果竞赛赛场上遇到,推荐使用word文档进行查找修改,不容易出错)

inline void RR_reverse(int root){
	int RR=root->Rchild;
	root->Rchild = RR->Rchild;// L儿子给root当R儿子
	//root->Lchild 依然为 root->Lchild
	RR->Lchild=root;//root变儿子
	if (Fa[root]->Rchild == root) Fa[root]->Rchild = RR;//R儿子继位
	else (Fa[root]->Lchild == root) Fa[root]->Lchild = RR;//R儿子继位
}

- RL形调整
类似LR调整,根据对称性,Ctrl CV之后,把LR调整中的左右全部呼唤即可
(如果竞赛赛场上遇到,推荐使用word文档进行查找修改)

inline void RL_reverse(int root){
	int RL=root->Rchild->Lchild,R=root->Rchild;
	R->Lchild = RL->Rchild //左儿子给自己的父亲当右儿子
	root->Rchild = RL->Lchild;// 右儿子给root当左儿子
	//root->Lchild 依然为 root->Lchild
	RL->Lchild=root;//root变右儿子
	RL->Rchild=R;//L做左儿子
	if (Fa[root]->Lchild == root) Fa[root]->Lchild = RL;//LR儿子继位
	else (Fa[root]->Rchild == root) Fa[root]->Rchild = RL;//LR儿子继位
}

3> 红黑树(RB-Tree)

【略记】
考虑到每次mantian(平衡)操作都要较高的复杂度,所以希望适度平衡即可。
目的:保证左右子树的高度差不超过2倍

定义:

  1. 每个节点染色红色或者黑色两种颜色。
  2. 根节点与叶子节点为黑色
  3. 如果一点的节点为红色,其子节点一定为黑色;如果一个节点为黑色,其子节点颜色不做要求。
  4. 将 root-> leaf 路径中的黑色节点数,定义为“黑高”。

根据上述定义,**“高度差不超过两倍” ** transfer to “黑高相同即可”

Operations平衡操作:
首先理解,
插入操作时,考虑到如果插入黑色节点,黑高一定不同,所以插入节点一定为红。
如果其合适位置的父亲节点是黑,即可直接插入;否则,考虑变色、左旋、右旋操作。
Operation 1 : 变色
叔红要变色,父、叔变黑,爷爷变红。(如果爷爷为根节点,则改为黑)
叔黑看左右,右子树左旋,左子树右旋。
Operation 2 : 左旋
Operation 3 : 右旋

4> 索引查找

目的:提高磁盘中文件查找的效率

B树(B-树)

定义:
阶: 每个节点的子树个数<=阶(树的度)
要求:根的子树个数至少有两个;其余节点的子树个数,至少有 阶数/2 个子树;所有叶子节点必须在树的同一层上 ***
(相当于多路径平衡树)

节点存储信息:数字n,n个value值V [i], n个指针 cnt[i] 。 cnt[i]指向
每个节点中存储的相当于一个顺序序列)
效果:保证
左子树们* 中所有节点的所有value值都小于root中的任意value值;右子树们 均大于root。

查询:
在当前节点中,先进行二分查找,找到对应value直接返回,否则在对应位置进入子树。
时间复杂度 O( log阶数 * 高度 )
在这里插入图片描述
插入
查询+裂开 (裂开?gape、crack,刚背的考研词汇,嗯)
查询应当插入的位置,根据B-树的定义,考虑是否将当前点裂开
裂开的操作:将当前点的mid位置的value给父亲,左右两段分成两个点;判断父亲长度是否符合要求,如果超出长度,则继续向上裂变。
【⇒ 性质:只有根发生裂变,树的高度才会变高】
删除
将左儿子的最大值或右儿子的最小值代替自己,然后问题转化为删除左儿子的最大值(右儿子的最小值)
对于叶子节点上某点的删除:直接删除。
删除之后可能出现的情况:当前节点的序列长度过短,则向兄弟借(把兄弟的value给父亲,父亲的 value给自己)

B+树

把所有非叶子都做成索引,只有叶子做value值记录。

※ 常见考点:

二叉排序树/折半查找 的 “比较路径”/ “比较序列” 的合法性判断

基于性质:二分排序树的查找过程,一定是一直向下查找,一条路走下去的方式。即 当前节点的下一个节点一定是自己的儿子。
根据上述性质绘制查找树的该路径,判断是否可以成功绘制。

二分查找树的高度

二叉查找树为平衡二叉树,因此
总结点数n符合不等式 : 2h-1 <= n <= 2h-1
⇒ height = log(n+1) 向上取整

易错:区分二叉排序树、二叉查找树

二叉排序树: 是在对无序序列进行快速排序的过程中构造的树形结构,其 不一定是平衡树形, 有可能是一条链,其单次查询时间复杂度 范围为 ( log2n , n)
二叉查找树:实在对有序序列进行二分查找的过程中构造的树形结构,其一定是平衡树形,其查询的时间复杂度 一定为 O ( log2n )。

易错: 二分代码的边界判定

易错:哈希表 如果用探测法进行冲突处理,注意“表长”和“模数”可能不同,如果表长满足探测位置,则按表长来存。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GoesM

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

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

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

打赏作者

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

抵扣说明:

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

余额充值