一、查找的基本概念
查找——在数据集合中寻找满足某种条件的数据元素的过程称为查找
查找表(查找结构) —— 用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成
关键字 —— 数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。
查找长度——在查找运算中,需要对比关键字的次数称为查找长度
平均查找长度(ASL,Average Search Length)——所有查找过程中进行关键字的比较次数的平均值
静态查找表:只作查找操作的查找表。
动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。
二、顺序查找
代码如下:
#include<stdio.h>
typedef int ElemType;
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 = ST.TableLen ? -1 : i;//查找成功返回元素下表,失败返回-1
}
}
三、有序表查找
3.1 折半查找
折半查找,又称“二分查找”,仅适用于有序的顺序表。
mid=(low+high)/2
3.2 差值查找
mid=low+(high-low)*(key-a[low])/(a[high]-a[low])
四、线索索引查找
4.1 分块查找
分块有序,把数据集的记录分成若干块,并且这些块需要满足:块内无序、块间有序
4.2 倒排索引
倒排索引:其中记录号表储存具有相同次关键字的所有记录的记录号(可以是指向记录的指针或者是该记录的主关键词)
五、二叉树排序
二叉排序树,又称二叉查找树(Binary Search Tree)一颗二叉树或者是空二叉树,或者是具有如下性质的二又树:
左子树上所有结点的关键字均小于根结点的关键字:
右子树上所有结点的关键字均大于根结点的关键字。
左子树结点值< 根结点值< 右子树结点值。
左子树和右子树又各是一棵二叉排序树。
进行中序遍历,可以得到一个递增的有序序列。
4.1 二叉排序树的查找
下面为二叉排序树的查找操作(代码如下):
//二叉排序树结点
typedef struct BSTNode
{
int key;
struct BSTNode* lchild, * rchild;
}BSTNode, * BSTree;
//在二叉排序树中查找值为 key 的结点
BSTNode* BST_Search(BSTree T, int key)
{
while (T != NULL && key != T->key) //若树空或等于根结点值,则结束循环
{
if (key < T->key) T = T->lchild; //小于,则在左子树上查找
else T = T->rchild;//大于,则在右子树上查找
}
return T;
}
采用递归版本的二叉查找算法如下:
//在二叉排序树中查找值为 key 的结点 (递归实现)
BSTNode* BSTSearch(BSTree T, int key)
{
if (T == NULL)
return NULL;//查找失败
if (key == T->key)
return T;//查找成功
else if (key < T->key)
return BSTSearch(T->lchild,key); //在左子树中找
else
return BSTSearch(T->rchild,key); //在右子树中找
}
4.2 二叉排序树的删除
先搜索找到目标结点:
1、若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。
2、若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。
3、若结点z有左、右两棵子树,则令z的直接后继 (或直接前驱) 替代z,然后从二叉排序树中删去这个直接后继 (或直接前驱),这样就转换成了第一或第二种情况。
使用z的后继代替z时: 采用z的右子树中最左下结点(该节点一定没有左子树))
使用z的前驱代替z时: 采用z的左子树中最右下结点(该节点一定没有右子树))
六、平衡二叉树
平衡二叉树(Balanced BinaryTree),简称平衡树 (AVL)——树 上任一结点的左子树和右子树的
高度之差不超过1。
结点的平衡因子 = 左子树高 - 右子树高。
6.1 调整最小不平衡子树(LL)
(1)LL平衡旋转(右单旋转)。由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。
6.2 调整最小不平衡子树(RR)
(2)RR平衡旋转(左单旋转)。由于在结点A的右孩子(R)的子树(R)上插入了新结点,A的平衡因子由-1减至-2导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转代替A成为根结点,)将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。
6.3 调整最小不平衡子树(LR)
(3)LR平衡旋转(先左后右双旋转)。由于在A的左孩子(L)的右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。
6.4 调整最小不平衡子树(RL)
(4)RL平衡旋转(先右后左双旋转)。由于在A的右孩子 (R)的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置。
6.5 四种情况小结
只有左孩子才能右上旋, 只有右孩子才能左上旋。
6.6 平衡二叉树的删除
平衡二叉树的删除操作:
1、删除结点后,要保持二又排序树的特性不变(左<中<右 )
2、若删除结点导致不平衡,则需要调整平衡
平衡二叉树的删除操作具体步骤
1. 删除结点 (方法同“二叉排序树”)
2. 一路向北找到最小不平衡子树,找不到就完结撒花
3. 找最小不平衡子树下,“个头”最高的儿子、孙子
4. 根据孙子的位置,调整平衡 (LL/RR/LR/RL)
5. 如果不平衡向上传导,继续2
七、红黑树
红黑树是二叉排序树的一种:左子树结点值 < 根结点值< 右子树结点值
7.1 红黑树的特性
1. 每个结点或是红色,或是黑色的
2. 根节点是黑色的
3. 叶结点 (外部结点、NULL结点、失败结点) 均是黑色的
4. 不存在两个相邻的红结点 (即红结点的父节点和孩子结点均是黑色)
5. 对每个结点,从该节点到任一叶结点的简单路径上,所含黑结点的数目相同
记忆口诀:左根右,根叶黑,不红红,黑路同。
性质1: 从根节点到叶结点的最长路径不大于最短路径的2倍
性质2: 有n个内部节点的红黑树高度
红黑树的定义方式
7.2 红黑树的插入
1、先查找,确定插入位置(原理同二叉排序树),插入新结点
2、新结点是根——染为黑色
3、新结点非根一一染为红色
3.1 若插入新结点后依然满足红黑树定义,则插入结束
3.2 若插入新结点后不满足红黑树定义,需要调整,使其重新满足红黑树定义
黑叔: 旋转+染色。
1. LL型:右单旋,父换爷+染色
2. RR型: 左单旋,父换爷+染色
3. LR型:左、右双旋,儿换爷+染色
4. RL型:右、左双旋,儿换爷+染色
红叔: 染色+变新。
叔父爷染色,爷变为新结点
7.3 红黑树的删除
1. 红黑树删除操作的时间复杂度
2. 在红黑树中删除结点的处理方式和 “二叉排序树的删除” 一样
3. 按2删除结点后,可能破坏“红黑树特性”,此时需要调整结点颜色、位置,使其再次满足 “红黑树特性”
八、B树
策略: m叉查找树中,规定除了根节点外,任何结点至少有 [m/2] 个分叉,即至少含有 [m/2]-1 个关键字。
m叉查找树中,规定对于任何一个结点,其所有子树的高度都要相同。
8.1 B树的概念
B树,又称多路平衡查找树,B树中所有结点的孩子个数的最大值称为B树的阶,通常用m表示。一棵m阶B树或为空树,或为满足如下特性的m叉树:
1)树中每个结点至多有m棵子树,即至多含有m-1个关键字。
2)若根结点不是终端结点,则至少有两棵子树。
3) 除根结点外的所有非叶结点至少有「m/2] 棵子树,即至少含有[m/2]-1个关键字。
4) 关键字由小到大按顺序排列
5) 所有的叶结点都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查找判定树的查找失败结点,实际上这些结点不存在,指向这些结点的指针为空)。
m阶B树的核心特性:
1) 根节点的子树数∈[2, m],关键字数∈[1, m-1]。
其他结点的子树数∈[ [m/2], m];关键字数∈ [[m/2]-1, m-1])
2)对任一结点,其所有子树高度都相同
3)关键字的值:子树0<关键字1<子树1<关键字2<子树2<....(类比二叉查找树左<中<右)
含n个关键字的m叉B树,
8.2 B树的插入
核心要求:
① 对m阶B树——除根节点外,结点关键字个数[m/2]-1≤n≤m-1
② 子树0<关键字1<子树1<关键字2<子树2<....
新元素一定是插入到最底层“终端节点”,用“查找”来确定插入位置
在插入key后,若导致原结点关键字数超过上限,则从中间位置([m/2])将其中的关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置([m/2])的结点插入原结点的父结点。若此时导致其父结点的关键字个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根结点为止,进而导致B树高度增1。
8.3 B树的删除
1、若被删除关键字在终端节点,则直接删除该关键字(要注意节点关键字个数是否低于下限「m/2]- 1)
2、若被删除关键字在非终端节点,则用直接前驱或直接后继来替代被删除的关键字
直接前驱:当前关键字左侧指针所指子树中“最右下”的元素。
直接后继:当前关键字右侧指针所指子树中“最左下”的元素。
3、若被删除关键字所在结点删除前的关键字个数低于下限且与此结点右(或左)兄弟结点的关键字个数还很宽裕,则需要调整该结点、右(或左)兄弟结点及其双亲结点(父子换位法)。
当右兄弟很宽裕时,用当前结点的后继、后继的后继来填补空缺;当左兄弟很宽裕时,用当前结点的前驱、前驱的前驱来填补空缺。
4、若被删除关键字所在结点删除前的关键字个数低于下限,且此时与该结点相邻的左、右兄弟结点的关键字个数均=[m/2]一1,则将关键字删除后与左(或右〉兄弟结点及双亲结点中的关键字进行合并。
8.4 B+树
一棵m阶的B+树需满足下列条件︰
1) 每个分支结点最多有m棵子树(孩子结点)。
2) 非叶根结点至少有两棵子树,其他每个分支结点至少有「m/2] 棵子树
3) 结点的子树个数与关键字个数相等。
4) 所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
5) 所有分支结点中仅包含它的各个子结点中关键字的最大值及指向其子结点的指针。
在B+树中,叶结点包含信息,所有非叶结点仅起索引作用,非叶结点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。
九、散列表查找(哈希表)
散列表(Hash Table),又称哈希表。是一种数据结构,特点是: 数据元素的关键字与其存储地址直接相关。
查找长度—— 在查找运算中, 需要对比关键字的次数称为查找长度。
9.1 常见的散列函数
一、除留余数法—— H(key)= key % p
散列表表长为m,取一个不大于m但最接近或等于m的质数p
二、直接定址法——H(key) = key 或 H(key) = a*key + b
其中, a和b是常数。这种方法计算最简单,且不会产生冲突。它适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。
三、数字分析法——选取数码分布较为均匀的若干位作为散列地址
设关键字是r进制数〈如十进制数),而r个数码在各位上出现的频率不一定相同,可能在某些位上分布均匀一些,每种数码出现的机会均等﹔而在某些位上分布不均匀,只有某几种数码经常出现,此时可选取数码分布较为均匀的若干位作为散列地址。这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。
四、平方取中法——取关键字的平方值的中间几位作为散列地址。
具体取多少位要视实际情况而定。这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均匀,适用于关键字的每位取值都不够均匀或均小于散列地址所需的位数。
9.2 处理冲突的办法
9.2.1 开放定址法
所谓开放定址法,是指可存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开放。其数学递推公式为︰ H =(H(key)+ di)% m
m表示散列表表长; d 为增量序列; i 可理解为“ 第 i 次发生冲突 ”。
1、线性探测法—— d=0,1,2,3,..., m-1; 即发生冲突时,每次往后探测相邻的下一个单元是否为空
注意: 采用 “开放定址法” 时,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填入散列表的同义词结点的查找路径,可以做一个“删除标记”,进行逻辑删除。
缺点:线性探测法很容易造成同义词、非同义词的“聚集 (堆积)”现象,严重影响查找效率
产生原因——冲突后再探测一定是放在某个连续的位置
2. 平方探测法
其中 、
、
、
、
......
9.2.2 再散列法
再散列法 (再哈希法) : 除了原始的散列函数 H(key)之外,多准备几个散列函数当散列函数冲突时,用下一个散列函数计算一个新地址,直到不冲突为止。