前面的文章中写的每一种数据结构都定义了一些常用的操作,如:初始化、访问数据元素等等,因此,研究操作的实现(不是操作的定义)即算法与数据结构密不可分。
有两个操作在每个数据结构上一般都要定义,而且是非常重要的:
·确定元素的位置——搜索(查找);
将元素按某种书序排列——排序(分类)。
这篇文章我主要写一下搜索(查找)的一些知识(算法基本思想、效率、优缺点、适用范围等)。
一、静态表的查找
二、动态表查找
三、HASH(哈希)查找
1.查找:在某一数据集合中查找数据元素是否存在,如果存在,返回其位置,否则,返回失败信息。
2.查找表:被查找数据元素的集合,如果是逻辑结构上定义的操作,那么位置就是逻辑位置;如果有了存储结构,那么位置应该就是物理位置。因此,查找表一般是“数据结构+存储结构”。
3.查找表的种类:
(1)静态查找表;数据集合在查找前后不变(没有插入、删除);
(2)动态查找表:数据集合在查找前后会改变(有插入、删除)。
4.关键码:可以唯一的标识一个数据元素的数据项(属性)。
基于关键码的查找,结果必是唯一的;基于一般属性的查找,结果可能不唯一。
5.查找方法:
查找表不同,查找方法就会不同。有很多不同的查找方法。
6.查找算法的评价:
(1)空间:占用的辅助空间少;
时间:时间少。查找的基本操作是比较,因此 时间主要体现为比较次数。
(2)查找成功:
最大比较次数——MSL(Maximum Serach Length)
平均比较次数——ASL(Average Serach Length)
(3)查找失败:
最大比较次数——MSL
平均比较次数——ASL
一、静态表的查找
1. 静态查找表是顺序或链式存储的线性表 ——顺序查找
(1)查找表的要求:只要是线性表即可;
(2)查找方法:不断找前驱或后继。
(3)特点:
1)思想简单,对查找表要求少,适应面广;
2)比较次数较大O(n)——成功、失败。
2.静态查找表是顺序存储的、有序的线性表——折半查找(Fibonacci查找、插值查找)
(1)查找表的要求:顺序存储、有序的线性表
(2)查找方法:
(3)特点:
1)对查找表要求多;
2)比较次数较少O(log以2为底n的对数)。
折半查找的过程可以用一棵二叉树表示,称之为“折半查找的判定树”。
3.静态查找表是二叉树——静态二叉排序树查找
(1)查找表的要求:二叉排序树
(2)查找方法:
X与根比较:相等则输出;X<根:在左子树上找;X>根,在右子树上找。
(3)特点:
类似折半,最大比较次数是树的深度;等概率时,深度为log2(n)二叉排序树最好;不等概率时,概率高的应该靠近根。
4.静态查找表是分块索引表——分块查找(索引顺序查找)
(1)查找表的要求:顺序存储、分块有序;即:每一块中的结点不必有序,但块与块之间必须"按块有序"。
(2)查找方法:
1)先选取各块中的最大关键字构成一个索引表;
2)折半方法确定被查找元素可能所在的块;
3)在块中采用顺序查找,确定元素是否存在;
(3)特点:
1)要建立索引表;
2)效率介于折半和顺序之间。
二、动态表查找
查找表本身是在查找过程中动态生成的,即对于给定值 key,若查找表中存在其关键码等于key的元素,则查找成功 返回,否则插入关键码等于key的元素。
动态查找表可以是线性表,也可以是树(二叉排序树)。最常用的动态查找表是二叉排序树。
1.二叉排序树动态查找方法:
若二叉排序树为空,则查找不成功,在二叉排序树中插入 该元素;否则
(1)若给定值等于根结点的关键字,则查找成功;
(2)若给定值小于根结点的关键字,则继续在左子树上进 行查找;
(3)若给定值大于根结点的关键字,则继续在右子树上进 行查找。
2.特点:查找效率与构造出的二叉排序树的深度有关。
对于每一棵特定的二叉排序树,均可按照平均查找 长度的定义来求它的ASL值:
其中,pi是查找第i个元素的概率,通常假设为等概率; ci是查找第i个元素的比较次数,这里ci=hi。
显然,由n个关键字构造所得的,不同形态的各棵 二叉排序树的平均查找长度的值不同,甚至可能差 别很大,例如:
对于同一个关键码集合,因为关键码插入的顺序不同,可以得到不同的二叉排序树。
3.平衡二叉树
(1)定义:一棵二叉排序树是平衡的,当且仅当每个结点的左右子树的高度至多相差1。(由G.M.Adelson_Velskii 和E.M.Landis给出的定义——AVL树)
(2)递归定义:
1)空树是二叉排序树;
2)它的左右子树都是二叉排序树,且左右子树 的高度最多相差为1。
(3)平衡因子:
左子树高度-右子树高度,即BF(t)=H(l)-H(r),平衡二叉树中,对任意节点,BF=1、0、-1。
(4)平衡二叉树的特点:
其深度和log以2为底n的对数同数量级,即AVL树的平局查找长度为O(log以2为底n的对数)。
(5)AVL树的构造和调整过程:
1)基本原则:按照二叉排序树的构造方法,构造过程中判断是否为平衡二叉树(平衡因子),是,则继续构造;否则, 按一定的原则(保持是二叉排序树和平衡)将其调整为平衡,然后继续。
2)插入过程中的调整原则:
二叉排序树在插入前平衡,插入一个结点后如果失去平衡,则至少有一个结点的平衡因子变为+2或-2。 若平衡因子=+2,则左分支高于右分支;若平衡因子=-2,则右分支高于左分支;
分为四种情况,分别进行调整:
LL型:在左分支的左子树上插入后,失去平衡,BF=2
LR型:在左分支的右上子树插入后,失去平衡,BF=2
RR型:在右分支的右子树上插入后,失去平衡,BF=-2
RL型:在右分支的左子树上插入后,失去平衡,BF=-2
LL型:
RR型:
对于LL型和RR型,如果失衡,就把它“掰”向另一边,当然,这个动作看上去很容易,但是用代码实现起来还是比较复杂的。
LR型:
RL型:
(6)查找过程:同静态二叉排序树是一样的。
(7)效率分析:
查找过程中和给定值进行比较的关键字的个数不超过 平衡树的深度。
假设深度为h的二叉平衡树上所含结点数的最小值为 Nh,则显然 Nh = N(h-1) + N(h-2) + 1 ,由此可以推导出h约为log(n)(应当是以2为底的)。因此,在平衡树上进行查找的时间复杂度为O(log(n))。
三、HASH(哈希)查找
1.哈希技术概述
(1)哈希技术的提出背景:我们前面介绍的各种查找方法,其基本操作是“比较” 即通过比较得到元素的位置。那么,有没有不用“比较”的查找方法?
如果有一个函数,它能够根据要查找的关键字直接计算 出要查找元素的地址,该地址的内容为空,则查找的元素不 存在,否则,查找成功。显然,这样的查找不需要比较!
(2)HASH类问题的描述:
假设问题可能用到的关键字集合为U,|U|=n0,即该集合的元素个数为n0,而一个问题实际用到的关键字集合为S,|S|=n,n<<<
有一个函数H,其定义域是key∈U,值域是i∈0...m-1,它将关键字映射到存储空间地址上;
H(key)= i key ∈U, i∈0...m-1
(3)有关基本概念:
1)HASH函数:将元素按照关键字映射出存储空间地址的 函数,记作:H(key) ;
2)HASH地址:由HSAH函数计算出的数据元素的存储地址;
3)HASH表:存储元素的连续地址空间T;
4)HASH造表:利用HASH函数将元素存储到HASH表中的 过程;
5)HASH表的填充度(装填因子):
6)冲突:由于HASH函数的定义域是U,而S是U的任意一个小子集,映射地址空间是由S的大小确定的,因此,对于不同的关键字可能得到相同的HASH地址,即: 若 key1≠key2 , 而 H(key1)=H(key2),则称为冲突;
7)同义词: 若 H(key1)=H(key2),则key1和key2互称 为同义词。
(4)HASH技术的关键:
1)HASH函数的构造;
函数设计目标:使通过哈希函数得到的n个数据元素的哈希地址尽可能均匀地分布在m个连续内存单元上,同时使计算过程 尽可能简单以达到尽可能高的时间效率。
常用的哈希函数构造方法有:除法取余、直接定址法、数字分析法、折叠法、平方取中
除留余法:
特点:
①这是一种最简单、最常用的方法;
②P的选择很重要,选择不好会产生同义词;
③P一般取位素数或不包含小于20的质数的合数。
2)解决冲突的方法;
解决冲突的策略分为两类:
①闭散列方法(Closed Hashing):同义词放在HASH表中的 其他位置;(Open addressing,又称为开地址法);
a.开放地址法(闭散列):发生冲突后,按一定的原则寻找新的地址:
举个例子:
注意,图中有很多79,并不是真的有那么多79,而是将79从地址1开始逐渐后移,直到后移到有空位的地方为止。
b.再造HASH法(闭散列):
H(i)=RHi(key)
用一系列HASH函数计算地址。
这个方法的意思应该是另外再找一个HASH函数。
②开散列方法(Open Hashing):同义词放在HASH表之外的 空间中;(Separate chaining,又称为拉链法)。
a.链地址法(开散列):
将同义词存放在同一个链表中;
举个例子:
③还有一种方法叫做建立公共溢出区法:
除了HASH表外,开辟一个公共溢出区,一旦冲 突,将同义词放入公共溢出区;
最后,总结一下:
1.哈希查找的方法
给定关键字k:
(1)用给定的HASH函数计算出k的HASH地址; i=H(key)
(2)若该地址为空,则查找失败; 否则,若 T[i]=k ,则查找成功,返回地址; 否则,按HASH造表时解决冲突的方法计算出新的地址;
(3)重复(2)直到查找成功或失败。
2.性能分析
如果没有冲突:O(1);
有冲突,性能与下列因素有关:
HASH函数、解决冲突的方法,装填因子。
总之,HASH查找是一种很高效的查找方法,也是一种使用范围很广的方法。