查找
概念
- 查找表:由同一类型的数据元素(或记录)构成的集合。对查找表进行的操作:查找 检索 增加 删除
- 静态查找表:对查找表进行 查找 检索
- 动态查找表:对查找表进行 查找 检索 增加 删除
- 关键字:数据中某个数据项的值,用以标识一个数据元素
- 主关键字:数据项的唯一标识
- 次关键字:用以标识若干数据项
- 查找是否成功:查找表 中是否存在
- 平均查找长度(ASL)(查找成功时):为确定记录在查找表中的位置,需要和给定值进行比较的记录的个数的期望值称为算法在查找成功时的平均查找长度。 衡量查找算法好坏的依据。
- 数据元素类型定义
typedef struct {
KeyType key;//关键字域
...//其它域
}SElemType;
静态查找表
顺序查找:任意顺序
分块查找:基本有序
折半查找:有序
静态查找表的顺序存储结构
typedef struct {
ElemType *elem;//数据元素存储空间基址,建表时按实际长度分配,0号单元留空(哨兵)
int length;//表长度(有效元素的个数)
}
顺序查找
-
从表中第一个记录开始,逐个进行记录的关键字和给定值的比较
-
demo
int Search_Seq (SSTable ST,KeyType key) {
//在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则返回值为改元素在表中的位置,否则为0
int i;
ST.elem[0].key = key;//哨兵位置
for(i = ST.length; ST.elem[i].key != key;i--);//从后往前比,不用考虑越界的问题,注意length的含义
return i;
}
- 效率分析
顺序查找的平均查找长度:
Pi = 1/n(每个记录的查找概率相等的时候)
- 成功时:
:因为是顺序查找,所以如果有n个元素的话,查找的次数可能为1 2 3 …n ,一共n种可能,其总和再除以n(元素个数)就是平均查找长度 - 考虑进不成功的情况
查找不成功时,关键字的比较次数总是n+1次。
当查找成与不成功的概率相同且每个记录被查找的概率相同时:
折半查找
- 优点:效率比顺序查找高
- 缺点:只适用于有序表,限于顺序存储结构
- demo
int Search_Bin(SSTable ST,KeyType key) {
int low = 1;
int high = ST.length;
while(low <= high) {
mid = (low + high) / 2;
if(ST.elem[mid].key == key) return mid;//找到待查元素
else if(key < ST.elem[mid].key)
high = mid - 1;//继续在前半区间进行查找
else
low = mid + 1; //继续在后半区间进行查找
}
return 0;//顺序表中不存在待查元素
}//Search_Bin
- 判定树(由中间结点组成)
- 效率分析
索引查找(分块查找)
- 条件
- 将表分成几块,且表或者有序,或者分块有序;
- 建立索引表(每个结点含有最大关键字域和指向本块第一个结点的指针,且按关键字有序)
- 索引项的内容
- 关键字项:该子表内最大关键字
- 指针项:指示该子表的第一个记录在表中的位置(不一定是关键字项的索引)
- 在块内是是顺查找
动态查找表
二叉排序树
- 左小右大
- 对二叉排序树进行中序遍历可得到一个关键字的有序序列
- 一个无序序列可通过构造二叉排序树而变成一个有序序列。
- 构造树的过程就是对无序序列进行排序的过程。
- 二叉排序树既有类似于折半查找的特性,又采用了链表作存储结构
- 比较的关键字次数 = 此节点所在层次数
- 最多的比较次数 = 树的深度
- 概念
- 是一颗空树
- 非空
- 如果左子树不空,则左子树上的所有节点的值都小于它的根节点的值
- 如果右子树不空,则右子树上的所有结点的值都大于它的根节点的值
- 左右子树也是二叉排序树
- demo
BiTree SearchBST(BiTree T,KeyType key) {
//在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素
//若查找成功,则返回指向该数据元素结点的指针,否则返回空指针
if((!T)||T->data.key == key) return (T);/查找结束
else if (T->data.key > key) return (SearchBST(T->lchild,key));//在左子树中继续查找
else
return (SearchBST(T->rchild,key));//在右子树中继续查找
}//SearchBST
- 二叉排序树的插入
- 若二叉排序树为空树,则新插入的结点为根结点;
- 若二叉排序树非空,则新插入的结点必为一个新的叶子结
点,并且是查找不成功时查找路径上访问的最后一个结点
的左孩子或右孩子结点
- 二叉排序树的删除
- 要求:在删除某个结点之后,仍保持二叉排序树的特性
- *p 为叶子结点。因删除叶子结点不破坏树的结构,故只需修改 *p 双亲 *f 的指针:f -> lchild=NULL 或 f->rchild=NULL
- *p只有左子树或只有右子树
- 只有左子树,用p的左孩子代替p
- 只有右子树,用p的右孩子代替p
- *p既有左子树又有右子树
- 用p的直接前驱取代p
- 用p的直接后继取代p
- 用左子树取代*p
- 平衡二叉树(据说不考,最好看看)
- 左、右子树是平衡二叉树;
- 所有结点的左、右子树深度之差的绝对值≤ 1
- 含有 n 个结点的二叉排序树的平均查找长度和树的形态有关(最好的是二叉平衡树)
B-树
B+树
哈希表
- 记录的关键与记录在表中的存储位置之间存在一种对应(函数)关系。若记录的关键字为key,记录在表中的位置(哈希地址)为f(key),则称此函数f(x)为哈希函数(散列函数)
- 冲突现象
- 具有相同函数值的关键字对该哈希函数来说称作 同义词
- 哈希造表 == 散列
- 哈希地址 == 散列地址
- 哈希函数的构造方法:直接定址法 数字分析法 平方取中法 折叠法 除留余数法 随机数法
4. 处理冲突的方法
-1. 开放定址法
- 线性探测再散列:d = 1,2,3…,m-1
- 二次探测再散列(平方探测再散列):d = 12,-12,22,-22,32,-32,…,-k2(k<=m/2);
- 伪随机探测再散列(双散列函数探测再探测):d = 伪随机数散列
- 再哈希法
- 构造若干个哈希函数,当发生冲突时,计算另一个哈希地址
- 溢出区法
- 链地址法
- 将所有关键字为同义词的记录存储在一个单链表(同义词子表)中,并用一维数组存放头指针
- 哈希表的饱和程度——
- 装载因子 α = n/m值的大小
- n 表中填入的记录数
- m 哈希表的长度
- 哈希表的ASL是处理冲突的方法和装载因子的函数
一些知识点
- 哈希函数的两个条件:冲突较小,构造简单
- 不存在特别好与坏的哈希函数,要视情况而定
- 用链地址法解决冲突时,可以直接删除元素;用开放地址法或其他地址法解决冲突,在删除时,不能真正删除,否则将截断其后续同义词记录的查找路径,只能对被删除记录做标记
- 采用链地址法解决冲突时,若插入规定总是在链首,则插入任一个元素的时间都是相同的
- 再哈希法不易产生聚集
- 链地址中哈希值相同的元素在一个链表中,而在链表中查找元素的时间是不相同的,显然链首元素查找要比链尾的快
- 散列函数有一个共同的性质,即函数值应当以同等概率取其域的每个值
- 关于B-树
- 非叶结点至少有m/2(m为偶数),或m/2+1(m为奇数)棵子树
- 所有叶子都在同一层次上,
- 根结点至多有m棵子树
- 树中每个结点至多有m-1个关键字
- 根结点中的数据不一定是有序的,但根结点中数据的关键字是有序的
- B-和B+都能有效地支持随机检索
- 都是平衡的二叉树
- 都可用于文件的索引结构