目录
一.查找概论
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.散列表查找性能分析
是由同一类型的数据元素(或记录)构成的集合
是数据元素中某个数据项的值,又称为键值,用它可以标识一个数据元素。也可以标识一个记录的某个数据项(字段),称为关键码
若此关键字可以唯一地标识一个记录,则称此关键字为主关键字(Primary Key)
对于那些可以识别多个数据元素(或记录)的关键字,我们称为次关键字,次关键字也可以理解为是不以唯一标识一个数据元素(或记录)的关键字,它对应的数据项就是次关键码
它的主要操作有:
在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素
动态查找表的操作就是两个:
它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。
在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止
插值查找(Interpolation Search)是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式(key-a[low])/(a[high]-a[low])
索引就是把一个关键字与它对应的记录相关联的过程,一个索引由若干个索引项构成,每个索引项至少应包含关键字和其对应的记录在存储器中的位置等信息
它或者是一棵空树,或者是具有下列性质的二叉树。
/* 二叉树的二叉链表结点结构定义 */
/* 结点结构 */
typedef struct BiTNode
{
/* 结点数据 */
int data;
/* 左右孩子指针 */
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
/* 递归查找二叉排序树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);
}
/* 当二叉排序树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;
}
是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1
/* 二叉树的二叉链表结点结构定义 */
/* 结点结构 */
typedef struct BiTNode
{
/* 结点数据 */
int data;
/* 结点的平衡因子 */
int bf;
/* 左右孩子指针 */
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
/* 对以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;
}
/* 对以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;
}
其每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。由于它是查找树,所有元素之间存在某种特定的排序关系。
2-3树是这样的一棵多路查找树:其中的每一个结点都具有两个孩子(我们称它为2结点)或三个孩子(我们称它为3结点)
且与二叉排序树类似,左子树包含的元素小于该元素,右子树包含的元素大于该元素,这个2结点要么没有孩子,要有就有两个,不能只有一个孩子
如果某个3结点有孩子的话,左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树包含介于两元素之间的元素
对于2-3树的插入来说,与二叉排序树相同,插入操作一定是发生在叶子结点上。可与二叉排序树不同的是,2-3树插入一个元素的过程有可能会对该树的其余结构产生连锁反应
它其实就是2-3树的概念扩展,包括了4结点的使用。一个4结点包含小中大三个元素和四个孩子(或没有孩子),一个4结点要么没有孩子,要么具有4个孩子。如果某个4结点有孩子的话,左子树包含小于最小元素的元素;第二子树包含大于最小元素,小于第二元素的元素;第三子树包含大于第二元素,小于最大元素的元素;右子树包含大于最大元素的元素
结点最大的孩子数目称为B树的阶(order),因此,2-3树是3阶B树,2-3-4树是4阶B树。
B+树是应文件系统所需而出的一种B树的变形树
(1)散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f
(2)这种对应关系f称为散列函数,又称为哈希(Hash)函数
(3)按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。那么关键字对应的记录存储位置我们称为散列地址
(1)在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录
(2)当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录
(1)抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段
(2)数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法
折叠法是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址
此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:
f(key)=key mod p(p≤m)
mod是取模(求余数)的意思
(1).计算散列地址所需的时间
(2).关键字的长度
(3).散列表的大小
(4).关键字的分布情况
(5).记录查找的频率
(1)如果没有冲突,散列查找是我们本章介绍的所有查找中效率最高的,因为它的时间复杂度为O(1)
(2)散列查找的平均查找长度取决于的因素