概述
查找表:由同一类型的数据元素(或记录)构成的集合。
关键字:数据元素中某个数据项的值,又称为键值,用它可以标识一个数据元素。也可以标识一个记录的某个数据项(字段),我们称为关键码。
若此关键字可以唯一地标识一个记录, 则称此关键字为主关键字。这也就意味着,对不同的记录,其主关键字均不相同。主关键字所在的数据项称为主关键码。那么对于那些可以识别多个数据元素(或记录)的关键字,我们称为次关键字。次关键字也可以理解为是不以唯一标识一个数据元素(或记录)的关键字,它对应的数据项就是次关键码。
查找:根据给定的某个值,在查找表中确定个其关键字等于给定值的数据元素(或记录)。
静态查找表:只作查找操作的查找表。
动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。
顺序表查找
静态表查找
顺序查找又叫线性查找,是最基本的查找技术,它的查找过程是:从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录;如果直到最后一个(或第一个)记录,其关键字和给定值比较都不等时,则表中没有所查的记录,查找不成功。
优化方式为设置一个哨兵,此时代码是从尾部开始查找,由于a[0]=key,也就是说,如果在a[i]中有key则返回i值,查找成功。否则一定在最终的a[0]处等于key,此时返回的是0,即说明a[1]~a[n]中没有关键字key,查找失败。
这种在查找方向的尽头放置“哨兵”免去了在查找过程中每一次比较后都要判断查找位置是否越界的小技巧,看似与原先差别不大,但在总数据较多时,效率提高很大,是非常好的编码技巧。当然,“哨兵”也不一定就一 定要在数组开始,也可以在末端。
平均O(n) 最好O(1) 最坏O(n)
有序表查找
折半查找技术,又称为二分查找。它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。
平均O(log2n) 最好O(1) 最坏O(log2n)
斐波那契查找
它是利用了黄金分割原理来实现的。
如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去,对处于当中的大部分数据,其工作效率要高一些。 所以尽管斐波那契查找的时间复杂也为O(log2n),但就平均性能来说,斐波那契查找要优于折半查找。可惜如果是最坏情况,比如key=1,那么始终都处于左侧长半区在查找,则查找效率要低于折半查找。
索引查找
数据结构的最终目的是提高数据的处理速度,索引是为了加快查找速度而设计的一种数据结构。索引就是把一个关键字与它对应的记录相关联的过程,一个索引由若干个索引项构成,每个索引项至少应包含关键字和其对应的记录在存储器中的位置等信息。索引技术是组织大型数据库以及磁盘文件的一种重要技术。
索引按照结构可以分为线性索引、树形索引和多级索引。所谓线性索引就是将索引项集合组织为线性结构,也称为索引表。主要有三种线性索引:稠密索引、分块索引和倒排索引。
稠密索引
稠密索引是指在线性索引中,将数据集中的每个记录对应一个索引项。
索引项一定是按照关键码有序的排列,但是如果数据集非常大,比如上亿,那也就意味着索引也得同样的数据集长度规模,对于内存有限的计算机来说,可能就需要反复去访问磁盘,查找性能反而大大下降了。
分块索引
分块有序,是把数据集的记录分成了若干块,并且这些块需要满足两个条件:块内无序,即每一块内的记录不要求有序。块间有序,要求第二块所有记录的关键字均要大于第一块中所有记录的关键字,第三块的所有记录的关键字均要大于第二块的所有记录关键字......因为只有块间有序,才有可能在查找时带来效率。
索引项结构分三个数据项:
1、最大关键码,它存储每一块中的最大关键字,这样的好处就是可以使得在它之后的下一块中的最小关键字也能比这一块最大的关键字要大;
2、存储了块中的记录个数,以便于循环时使用;
3、用于指向块首数据元素的指针,便于开始对这一块中记录进行遍历。
二叉排序树
二叉排序树,又称为二叉查找树。它或者是一棵空树, 或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。
从二叉排序树的定义也可以知道,它前提是二叉树,然后它采用了递归的定义方法,再者,它的结点间满足一定的次序关系,左子树结点一定比其双亲结点小,右子树结点一定比其双亲结点大。
平均O(log2n) 最好O(1) 最坏O(n)
平衡二叉树(AVL树)
平衡二叉树是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1。要么它是一棵空树, 要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上所有结点的平衡因子只可能是-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。
平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。
构建平衡二叉树的过程中,有四种旋转方式,不在考察范围内,可以自行了解。
平均O(log2n) 最好O(1) 最坏O(log2n)
HASH
散列函数:把关键字k映射成内存单元的地址H(k),并将关键字存储到这个内存单元中,函数H(k)即哈希函数。要求是减少冲突和提高效率。除留余数法(选p为不超过某基本区域长度的最大素数);数字分析法(删去多余分布不均匀的位数);平方取中法;随机乘数法;折叠法(位数较多且各位分布较均匀);基数转换法。
冲突/碰撞:相同散列值的关键码值被关联到同一个表项。
负载因子α:α= 散列表中结点数目 / 散列表的长度。
解决冲突的方法:开放地址法;链表地址法。
开放地址法:每个表项有一个是否被占用的标志,当试图加入新的数据时,首先判断是否占用,如果位置已被占用则寻找开放的表项。但会造成堆积(对应表项被非同义词占据。选择不当,负载因子过大。),不能随便删除表项。
线性探查法:d,d+1,d+2,……,n-1,0,1,……,d-1。
双散列探查法:用hash1产生基本散列值,当冲突发生时,利用hash2计算探测序列,使探测序列跳跃式地分布在整个存储区域,减少堆积产生。
链表地址法:当碰撞发生时就拉出一条链,建立一个链接同义词子表。每个表项需要增加一个指针域。
独立链表地址法(分离):开辟一个溢出区存储同义词。(最优:速度快,动态分配)
公共链表地址法(结合):从后往前探测,把同义词子表存储在基本区域中目前还没有被占用的单元里。
此处的堆积指的是两个同义词表结合在一起的现象,可改用链表地址法解决碰撞;用两遍处理的方法:第一遍只插入作为各同义词子表表头的那些关键码,第二遍再插入其它关键码;用双链表组织结合的同义词子表:建立散列表发生碰撞时,若判断出不是同义词,可以修改原已建立的同义词子表,使不同的同义词子表分开。
效率:独立链表>公共链表>双散列>线性探查法。
参考教材:
《数据结构与算法》,熊岳山著,清华大学出版社,2016,第二版
《2020年数据结构考研复习指导》,电子工业出版社,2020