目录
一.查找概论
1.概念:
2.查找表按照操作方式来分有两大种:静态查找表和动态查找表
3.面向查找操作的数据结构称为查找结构
二.顺序表查找
1.顺序查找(Sequential Search):又叫线性查找,是最基本的查找技术
2.顺序表查找算法:
3.顺序表查找算法优化
三.有序表查找
1.折半查找
2.差值查找
3.斐波那契查找
4.三种有序表查找的区别在于:
四.线性索引查找
1.含义
2.稠密索引
3.分块索引
4.倒排索引
五.二叉排序树
1.含义:
2.二叉排序树查找操作
3.二叉排序树插入操作
4.二叉排序树的删除操作
5.二叉排序树总结
六.平衡二叉树
1.含义
2.平衡二叉树的实现原理
3.平衡二叉树实现算法
七.多路查找树(B树)
1.含义
2.2-3树
3.2-3-4树
4.B树
5.B+树
八.散列表查找(哈希表)概述
1.散列表查找定义
2.散列表查找步骤
3.散列主要是面向查找的存储结构
九.散列函数的构造方法
1.好的散列函数的两个原则
2.直接定址法
3.数字分析法
4.平方取中法
5.折叠法
6.除留余数法
7.采用不同散列函数的因素
十.处理散列冲突的方法
1.开放定址法
2.再散列函数法
3.链地址法
4.公共溢出区法
十一.散列表查找实现
1.散列表查找算法实现
2.散列表查找性能分析
一.查找概论
1.概念:
(1)查找表(Search Table)
是由同一类型的数据元素(或记录)构成的集合
(2)关键字(Key)
是数据元素中某个数据项的值,又称为键值,用它可以标识一个数据元素。也可以标识一个记录的某个数据项(字段),称为关键码
(3)主关键字:
若此关键字可以唯一地标识一个记录,则称此关键字为主关键字(Primary Key)
(4)次关键字:
对于那些可以识别多个数据元素(或记录)的关键字,我们称为次关键字,次关键字也可以理解为是不以唯一标识一个数据元素(或记录)的关键字,它对应的数据项就是次关键码
2.查找表按照操作方式来分有两大种:静态查找表和动态查找表
(1)静态查找表(Static Search Table):只作查找操作的查找表。
它的主要操作有:
- 查询某个“特定的”数据元素是否在查找表中
- 检索某个“特定的”数据元素和各种属性
(2)动态查找表(Dynamic Search Table):
在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素
动态查找表的操作就是两个:
- 查找时插入数据元素
- 查找时删除数据元素
3.面向查找操作的数据结构称为查找结构
二.顺序表查找
1.顺序查找(Sequential Search):又叫线性查找,是最基本的查找技术
2.顺序表查找算法:
3.顺序表查找算法优化:
三.有序表查找
1.折半查找
(1)折半查找(Binary Search)技术,又称为二分查找。
它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。
(2)折半查找的基本思想是:
在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止
(3)折半算法的时间复杂度为O(logn)
2.插值查找
插值查找(Interpolation Search)是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式(key-a[low])/(a[high]-a[low])
3.斐波那契查找
(1)斐波那契查找(Fibonacci Search),是利用黄金分割原理来实现的
(2)“斐波那契查找算法的核心在于:
- 当key=a[mid]时,查找就成功;
- 当key<a[mid]时,新范围是第low个到第mid-1个,此时范围个数为F[k-1]-1个;
- 当key>a[mid]时,新范围是第m+1个到第high个,此时范围个数为F[k-2]-1个
4.三种有序表查找的区别在于:
- 折半查找是进行加法与除法运算(mid=(low+high)/2),
- 插值查找进行复杂的四则运算(mid=low+(high-low)*(key-a[low])/(a[high]-a[low])),
- 而斐波那契查找只是最简单加减法运算(mid=low+F[k-1]-1)
四.线性索引查找
1.含义:
(1)索引是为了加快查找速度而设计的一种数据结构。
索引就是把一个关键字与它对应的记录相关联的过程,一个索引由若干个索引项构成,每个索引项至少应包含关键字和其对应的记录在存储器中的位置等信息
(2)索引按照结构可以分为线性索引、树形索引和多级索引
(3)所谓线性索引就是将索引项集合组织为线性结构,也称为索引表
2.稠密索引
(1)稠密索引是指在线性索引中,将数据集中的每个记录对应一个索引项
(2)索引项有序也就意味着,我们要查找关键字时,可以用到折半、插值、斐波那契等有序查找算法,大大提高了效率
3.分块索引
(1)分块有序,是把数据集的记录分成了若干块,并且这些块需要满足两个条件:
- 块内无序,即每一块内的记录不要求有序
- 块间有序
(2)定义的分块索引的索引项结构分三个数据项:
- 最大关键码,它存储每一块中的最大关键字,这样的好处就是可以使得在它之后的下一块中的最小关键字也能比这一块最大的关键字要大;
- 存储了块中的记录个数,以便于循环时使用;
- 用于指向块首数据元素的指针,便于开始对这一块中记录进行遍历
(3)在分块索引表中查找,就是分两步进行:
- 一是在分块索引表中查找要查关键字所在的块
- 二是根据块首指针找到相应的块,并在块中顺序查找关键码
4.倒排索引
(1)倒排索引的索引项通用结构是:
- 次关键码,例如上面的“英文单词”;
- 记录号表,例如上面的“文章编号”。
(2)其中记录号表存储具有相同次关键字的所有记录的记录号,这样的索引方法就是倒排索引
(3)由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引
五.二叉排序树
1.含义:
(1)二叉排序树(Binary Sort Tree),又称为二叉查找树。
它或者是一棵空树,或者是具有下列性质的二叉树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
2.二叉排序树查找操作
(1)二叉树结构
/* 二叉树的二叉链表结点结构定义 */
/* 结点结构 */
typedef struct BiTNode
{
/* 结点数据 */
int data;
/* 左右孩子指针 */
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
(2)二叉树插入操作
/* 递归查找二叉排序树T中是否存在key, */
/* 指针f指向T的双亲,其初始调用值为NULL */
/* 若查找成功,则指针p指向该数据元素结点,并
返回TRUE */
/* 否则指针p指向查找路径上访问的最后一个结点
并返回FALSE */
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
/* 查找不成功 */
if (!T)
{
*p = f;
return FALSE;
}
/* 查找成功 */
else if (key == T->data)
{
*p = T;
return TRUE;
}
else if (key < T->data)
/* 在左子树继续查找 */
return SearchBST(T->lchild, key, T, p);
else
/* 在右子树继续查找 */
return SearchBST(T->rchild, key, T, p);
}
3.二叉排序树插入操作
/* 当二叉排序树T中不存在关键字等于key的数据元
素时, */
/* 插入key并返回TRUE,否则返回FALSE */
Status InsertBST(BiTree *T, int key)
{
BiTree p, s;
/* 查找不成功 */
if (!SearchBST(*T, key, NULL, &p))
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if (!p)
/* 插入s为新的根结点 */
*T = s;
else if (key < p->data)
/* 插入s为左孩子 */
p->lchild = s;
else
/* 插入s为右孩子 */
p->rchild = s;
return TRUE;
}
else
/* 树中已有关键字相同的结点,不再插入 */
return FALSE;
}
4.二叉排序树的删除操作:
(1)删除结点三种情况:
- 叶子结点;
- 仅有左或右子树的结点;
- 左右子树都有的结点,我们来看代码,下面这个算法是递归方式对二叉排序树T查找key,查找到时删除
5.二叉排序树总结:
(1)二叉排序树是以链接的方式存储,保持了链接存储结构在执行插入或删除操作时不用移动元素的优点,只要找到合适的插入和删除位置后,仅需修改链接指针即可
(2)一个集合按二叉排序树查找,最好是把它构建成一棵平衡的二叉排序树
六.平衡二叉树
1.含义:
(1)平衡二叉树(Self-Balancing Binary SearchTree或Height-Balanced Binary Search Tree)
是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1
(2)要么它是一棵空树,要么它的左子树和右子树都是平衡二叉树
(3)距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树
2.平衡二叉树的实现原理:
(1)当最小不平衡子树根结点的平衡因子BF是大于1时,就右旋,小于-1时就左旋
3.平衡二叉树实现算法:
(1)二叉树结构定义
/* 二叉树的二叉链表结点结构定义 */
/* 结点结构 */
typedef struct BiTNode
{
/* 结点数据 */
int data;
/* 结点的平衡因子 */
int bf;
/* 左右孩子指针 */
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
(2)右旋操作:
/* 对以p为根的二叉排序树作右旋处理, */
/* 处理之后p指向新的树根结点,即旋转处理之前
的左子树的根结点 */
void R_Rotate(BiTree *P)
{
BiTree L;
/* L指向P的左子树根结点 */
L = (*P)->lchild;
/* L的右子树挂接为P的左子树 */
(*P)->lchild = L->rchild;
L->rchild = (*P);
/* P指向新的根结点 */
*P = L;
}
(3)左旋操作
/* 对以P为根的二叉排序树作左旋处理, */
/* 处理之后P指向新的树根结点,即旋转处理之前
的右子树的根结点0 */
void L_Rotate(BiTree *P)
{
BiTree R;
/* R指向P的右子树根结点 */
R = (*P)->rchild;
/* R的左子树挂接为P的右子树 */
(*P)->rchild = R->lchild;
R->lchild = (*P);
/* P指向新的根结点 */
*P = R;
}
七.多路查找树(B树)
1.含义:
(1)多路查找树(muitl-way search tree),
其每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。由于它是查找树,所有元素之间存在某种特定的排序关系。
(2)四种特殊形式:2-3树、2-3-4树、B树和B+树
2.2-3树
(1)含义:
2-3树是这样的一棵多路查找树:其中的每一个结点都具有两个孩子(我们称它为2结点)或三个孩子(我们称它为3结点)
(2)一个2结点包含一个元素和两个孩子(或没有孩子),
且与二叉排序树类似,左子树包含的元素小于该元素,右子树包含的元素大于该元素,这个2结点要么没有孩子,要有就有两个,不能只有一个孩子
(3)一个3结点包含一小一大两个元素和三个孩子(或没有孩子),
如果某个3结点有孩子的话,左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树包含介于两元素之间的元素
(4)2-3树的插入实现:
对于2-3树的插入来说,与二叉排序树相同,插入操作一定是发生在叶子结点上。可与二叉排序树不同的是,2-3树插入一个元素的过程有可能会对该树的其余结构产生连锁反应
(5)2-3树插入分三种情况:
- 对于空树,插入一个2结点即可
- 插入结点到一个2结点的叶子上。应该说,由于其本身就只有一个元素,所以只需要将其升级为3结点即可
- 要往3结点中插入一个新元素。因为3结点本身已经是2-3树的结点最大容量(已经有两个元素),因此就需要将其拆分,且将树中两元素或插入元素的三者中选择其一向上移动一层
(6)2-3树的删除实现:
- 所删除元素位于一个3结点的叶子结点上, 只需要在该结点处删除该元素即可,不会影响到整棵树的其他结点结构
- 所删除的元素位于一个2结点上,即要删除的是一个只有一个元素的结点,分四种情形,情形一,结点的双亲也是2结点,且拥有一个3结点的右孩子。若要删除结点1,那么只需要左旋即可;情形二,结点的双亲是2结点,它的右孩子也是2结点;情形三,此结点的双亲是一个3结点;情形四,当前树是一个满二叉树的情况
- 所删除的元素位于非叶子的分支结点。此时通常是将树按中序遍历后得到此元素的前驱或后继元素,考虑让它们来补位即可
3.2-3-4树
(1)含义:
它其实就是2-3树的概念扩展,包括了4结点的使用。一个4结点包含小中大三个元素和四个孩子(或没有孩子),一个4结点要么没有孩子,要么具有4个孩子。如果某个4结点有孩子的话,左子树包含小于最小元素的元素;第二子树包含大于最小元素,小于第二元素的元素;第三子树包含大于第二元素,小于最大元素的元素;右子树包含大于最大元素的元素
4.B树
(1)含义:
结点最大的孩子数目称为B树的阶(order),因此,2-3树是3阶B树,2-3-4树是4阶B树。
(2)一个m阶的B树具有如下属性:
- 如果根结点不是叶结点,则其至少有两棵子树。
- 每一个非根的分支结点都有k-1个元素和k个孩子,其中。每一个叶子结点n都有k-1个元素,
- 所有叶子结点都位于同一层次。
- 所有分支结点包含下列信息数据
(3)在B树上查找的过程是一个顺指针查找结点和在结点中查找关键字的交叉过程
(4)至于B树的插入和删除,方式是与2-3树和2-3-4树相类似的,只不过阶数可能会很大而已
5.B+树
(1)含义:
B+树是应文件系统所需而出的一种B树的变形树
(2)在B树中,每一个元素在该树中只出现一次,有可能在叶子结点上,也有可能在分支结点上。而在B+树中,出现在分支结点中的元素会被当作它们在该分支结点位置的中序后继者(叶子结点)中再次列出。另外,每一个叶子结点都会保存一个指向后一叶子结点的指针
(3)一棵m阶的B+树和m阶的B树的差异在于:
- 有n棵子树的结点中包含有n个关键字;
- 所有的叶子结点包含全部关键字的信息,及指向含这些关键字记录的指针,叶子结点本身依关键字的大小自小而大顺序链接;
- 所有分支结点可以看成是索引,结点中仅含有其子树中的最大(或最小)关键字
(4)B+树的插入、删除过程也都与B树类似,只不过插入和删除的元素都是在叶子结点上进行而已
八.散列表查找(哈希表)概述
1.散列表查找定义:
(1)散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f
(2)这种对应关系f称为散列函数,又称为哈希(Hash)函数
(3)按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。那么关键字对应的记录存储位置我们称为散列地址
2.散列表查找步骤:
(1)在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录
(2)当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录
3.散列主要是面向查找的存储结构。
九.散列函数的构造方法
1.好的散列函数的两个原则:
(1)计算简单
(2)散列地址分布均匀
2.直接定址法
3.数字分析法:
(1)抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段
(2)数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法
4.平方取中法
5.折叠法:
(1)含义:
折叠法是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址
6.除留余数法:
此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:
f(key)=key mod p(p≤m)
mod是取模(求余数)的意思
6.随机数法:
7.采用不同散列函数的因素:
(1).计算散列地址所需的时间
(2).关键字的长度
(3).散列表的大小
(4).关键字的分布情况
(5).记录查找的频率
十.处理散列冲突的方法:
1.开放定址法
2.再散列函数法
3.链地址法
4.公共溢出区法
十一.散列表查找实现
1.散列表查找算法实现
2.散列表查找性能分析
(1)如果没有冲突,散列查找是我们本章介绍的所有查找中效率最高的,因为它的时间复杂度为O(1)
(2)散列查找的平均查找长度取决于的因素
- 散列函数是否均匀
- 处理冲突的方法
- 散列表的装填因子,所谓的装填因子α=填入表中的记录个数/散列表长度。α标志着散列表的装满的程度。当填入表中的记录越多,α就越大,产生冲突的可能性就越大