710 likes | 824 Views
数据结构 第九章 查找. 本章内容 9.1 查找的基本概念 9.2 静态查找表 9.3 动态查找表 9.4 哈希表. 9.1 查找的基本概念. 查找表 (Search Table) 查找表是由同一类型的数据元素 ( 或记录 ) 构成的集合。对查找表的操作主要有: 查询某个“特定的”数据元素是否在查找表中; 检索某个“特定的”数据元素的各种属性; 在查找表中插入一个数据元素; 从查找表中删去某个数据元素。 查找表分类 静态查找表 仅作查询和检索操作的查找表。 动态查找表
E N D
本章内容 9.1查找的基本概念 9.2静态查找表 9.3动态查找表 9.4哈希表
9.1 查找的基本概念 • 查找表(Search Table) 查找表是由同一类型的数据元素(或记录)构成的集合。对查找表的操作主要有: • 查询某个“特定的”数据元素是否在查找表中; • 检索某个“特定的”数据元素的各种属性; • 在查找表中插入一个数据元素; • 从查找表中删去某个数据元素。 • 查找表分类 • 静态查找表 仅作查询和检索操作的查找表。 • 动态查找表 在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已存在的某个数据元素 9-3
9.1 查找的基本概念 • 关键字(Key) 关键字是数据元素(或记录)中某个数据项的值,用以标识(识别)一个数据元素(或记录)。 • 主关键字:可以识别唯一的一个记录的关键字 • 次关键字:能识别若干记录的关键字 • 查找(Searching) 是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。 9-4
9.1 查找的基本概念 • 衡量查找算法的标准 • 时间复杂度; • 空间复杂度; • 平均查找长度ASL:定义为确定记录在表中的位置所进行的和关键字比较的次数的平均值 n ASL = ∑ PiCi i=1 • n为查找表的长度,即表中所含元素的个数 • Pi为查找第i个元素的概率(∑Pi=1) • Ci是查找第i个元素时同给定值K比较的次数 9-5
9.2 静态查找表 9.2.1顺序表的查找 顺序查找算法是顺序表(既无序表)的查找方法。在顺序查找算法中,以顺序表或线性链表表示静态查找表。 • 面临的问题 下标越界的检查,需要相当的时间和空间代价。解决的办法是,将ST.elem[0].key 置为key,所有元素检查完还没有找到时,在ST.elem[0]处一定找到。从而免去了检查下标越界的时间。 • 顺序查找算法 • 从表中最后一个记录开始 • 逐个进行记录的关键字和给定值的比较 • 若某个记录比较相等,则查找成功 • 若直到第1个记录都比较不等,则查找不成功 9-6
9.2 静态查找表 • 顺序查找算法描述 • 设置“哨兵”的目的是省略对下标越界的检查,提高算法执行速度。当然,哨兵也可以设置在高下标处。 int Search_Seq(SSTable ST, KeyType key) { // 若查找成功,返回位置 ST.elem[0].key = key; // “哨兵”, for (i=ST.length; ST.elem[i].key!=key; --i); // 从后往前找 return i; // 找不到时,i=0 } 9-7
62 5 13 19 21 37 56 62 75 80 88 92 i=8 i=9 i=10 i=11 i=7,找到 9.2 静态查找表 • 顺序查找示举例 在下列顺序表中寻找62,如果找到,给出其所在位置的下标。 0 1 2 3 4 5 6 7 8 9 10 11 监视哨兵 比较次数分析: 查找第n个元素: 1次 查找第n-1个元素:2次 ………. 查找第1个元素: n次 查找第i个元素: n+1-i次 查找失败: n+1次 比较次数=5 9-8
9.2 静态查找表 • 顺序查找性能分析 • 对顺序表而言,Ci=n-i+1 • 在等概率查找的情况下,Pi=1/n 平均查找长度 ASLss=n*P1 +(n-1)P2 +…+ 2Pn-1+ Pn = (n+1)/2 • 如果被查找的记录概率不等时, ASLss在 Pn≥Pn-1 ≥…≥P2≥P1时取极小值 • 若查找概率无法事先测定,则查找过程采取的改进办法是,在每次查找之后,将刚刚查找到的记录直接移至表尾的位置上。 • 顺序查找特点 • 优点: 1.简单 2.适应面广(对表的结构无任何要求) • 缺点: 1.平均查找长度较大 2.特别是当n很大时,查找效率很低 9-9
9.2 静态查找表 9.2.2 折半查找 折半查找算法是有序表的查找方法。在折半查找算法中,静态查找表按关键字大小的次序,有序地存放在顺序表中。 • 折半查找的原理 • 先确定待查记录所在的范围(前部分或后部分) • 逐步缩小(一半)范围直到找(不)到该记录为止 9-10
9.2 静态查找表 • 折半查找算法 • n个对象从小到大存放在有序顺序表ST中,k为给定值 • 设low、high指向待查元素所在区间的下界、上界,即low=1, high=n • 设mid指向待查区间的中点,即 mid=(low+high)/2 • 让k与mid指向的记录比较 若 k=ST[mid].key,查找成功 若 k<ST[mid].key,则high=mid-1 [上半区间] 若 k>ST[mid].key,则low=mid+1 [下半区间] • 重复3,4操作,直至low>high时,查找失败。 9-11
1 2 3 4 5 6 7 8 9 10 11 5 13 19 21 37 56 62 75 80 88 92 low=1 low=7 high=8 high=11 mid=7 mid=6 mid=9 9.2 静态查找表 • 折半成功查找举例:在下列有序表中用折半查找法查找62所在位置。Key = 62 62 第一步:low=1, high=11, mid=6 ST[mid]=ST[6]=56<62, 则改变low; 第二步:low=mid+1=7, mid=9 ST[mid]=ST[9]=80>62, 则改变high; 第三步:high=mid-1=8, mid=7 ST[mid]=ST[7]=62=62, 找到! 9-12
1 2 3 4 5 6 7 8 9 10 11 5 13 19 21 37 56 62 75 80 88 92 9.2 静态查找表 • 折半查找失败举例:在上例有序表中找61。 找61 high=6 low=7 • 当下界low大于上界high时,说明有序 表中没有关键字等于K的元素,查找失败 9-13
6 3 9 1 4 7 10 判定树 2 5 8 11 9.2 静态查找表 • 折半查找的性能分析 折半查找过程可以用二叉树(也叫判定树)来描述: • 判定树上每个结点需要的查找次数刚好为该结点所在的层数; • 查找成功时查找次数不会超过判定树的深度 • n个结点的判定树的深度为 log2n +1 • 比较次数最多不超过 log2n +1 • 折半查找的算法复杂度为 log2n +1 9-14
9.2 静态查找表 • 折半查找特点 • 折半查找的效率比顺序查找高,特别是查找表的长度很长时; • 折半查找只能适用于等概率有序表,并且以顺序存储结构存储。 9-15
9.2 静态查找表 9.2.3 分块查找 • 分块查找是一种索引顺序表(分块有序表)查找方法,是折半查找和顺序查找的简单结合; • 索引顺序表(分块有序表)将整个表分成几块,块内无序,块间有序 • 所谓块间有序是指后一块表中所有记录的关键字均大于前一块表中的最大关键字 9-16
索引表 1 2 3 4 5 6 7 8 9 10 11 92 75 21 5 19 13 21 75 56 62 37 88 80 92 主表 5 9 1 9.2 静态查找表 • 基本概念 • 主表:用数组存放待查记录,每个数据元素至少含有关键字域 • 索引表:每个结点含有最大关键字域和指向本块第一个结点的指针 9-17
索引表 1 2 3 4 5 6 7 8 9 10 11 92 75 21 5 19 13 21 75 56 62 37 88 80 92 主表 9 5 1 9.2 静态查找表 • 分块查找举例 • 采用折半查找方法在索引表中找到块(第2块) • 用顺序查找方法在主表对应块中找到记录(第3个记录) 找62 9-18
9.2 静态查找表 • 分块查找性能分析 • 若将长度为n的表分成b块,每块含s个记录,并设表中每个记录查找概率相等 • 用折半查找方法在索引表中查找索引块,ASL块间≈log2(n/s+1) • 用顺序查找方法在主表对应块中查找记录,ASL块内=s/2 • ASL≈log2(n/s+1) + s/2 9-19
9.3 动态查找表 • 动态查找表 如果应用问题涉及的数据量很大,而且数据经常发生变化,如图书馆经常购进图书,每购进新书,需将新书记录插入图书表,对这类表除了提供前面的介绍的查找外,还要提供动态查找功能: • 查找某个“特定”元素是否在表中,若不在,将该元素插入; • 查找某个“特定”元素是否在表中,若在,从表中删除; • 如何组织动态查找表? 用静态查找方法不能满足要求了。本节介绍几种方法。 9-20
56 13 64 56 64 13 5 37 92 5 37 92 60 21 80 21 80 88 75 19 88 75 19 9.3 动态查找表 9.3.1 二叉排序树 二叉排序树又称二叉查找树 • 空树或者是具有如下特性的二叉树: • 若左子树不空,则左子树上所有结点的值均小于根结点的值; • 若右子树不空,则右子树上所有结点的值均大于根结点的值; • 左、右子树也都分别是二叉排序树。 二叉排序树 非二叉排序树 9-21 21
56 64 13 5 37 92 21 80 88 75 19 9.3 动态查找表 • 二叉排序树查找算法 给定值与根结点比较: • 若相等,查找成功 • 若小于,查找左子树 • 若大于,查找右子树 37 找到 无此数 找到 例1:在右图二叉排序树中查找关键字值等于37 例2:在右图二叉排序树中查找关键字值等于88 例3:在右图二叉排序树中查找关键字值等于94 9-22
56 64 13 5 37 92 21 80 94 88 75 19 9.3 动态查找表 • 二叉排序树插入 二叉排序树是一种动态树表。 • 当树中不存在查找的结点时,作插入操作 • 新插入的结点一定是叶子结点(只需改动一个结点的指针) • 该叶子结点是查找不成功时路径上访问的最后一个结点左孩子或右孩子(新结点值小于或大于该结点值) 例:在右图二叉树中插入结点94。 9-23
64 92 80 75 88 9.3 动态查找表 • 二叉排序树生成 例:画出在初始为空的二叉排序树中依次插入56,64,92,80,88,75时该树的生长全过程 56 9-24
56 64 13 5 37 92 21 80 88 75 19 9.3 动态查找表 • 二叉排序树中序遍历 中序遍历二叉排序树,可得到一个关键字的有序序列,如5,13,19,21,37,56,64,92,75,80,88 9-25
9.3 动态查找表 • 二叉排序树删除 删除二叉排序树中的一个结点后,必须保持二叉排序树的特性:左子树的所有结点值小于根结点,右子树的所有结点值大于根结点。也即保持中序遍历后,输出为有序序列 • 被删除结点具有以下三种情况 • 是叶子结点 • 只有左子树或右子树 • 同时有左、右子树 • 下面针对三种情况各举一例。 9-26
9.3 动态查找表 • 被删除结点是叶子结点:直接删除结点,并让其父结点指向该结点的指针变为空。 • 例:删除右二叉排序树中的88节点 56 64 13 5 37 92 21 80 88 75 19 9-27
64 37 92 21 80 88 75 19 9.3 动态查找表 • 被删除结点只有左子树或右子树 删除结点,让其父结点指向该结点的指针指向其左子树(或右子树),即用孩子结点替代被删除结点即可 • 例:对于右边二叉排序树中,先删除节点37,再删除节点64。 56 13 5 9-28
21 19 9.3 动态查找表 • 被删除结点p既有左子树,又有右子树 以中序遍历时的直接前驱s替代被删除结点p,然后再按照前面介绍的发删除该直接前驱s(只可能有左孩子) 例:在右边二叉排序树中,节点56 的中序遍历的直接前趋是节点37。 56 64 13 5 37 92 80 88 75 9-29
92 88 80 75 64 5 13 19 21 37 56 9.3 动态查找表 • 二叉排序树性能分析 • 在最好的情况下,二叉排序树为一近似完全二叉树时,其查找深度为log2n量级,即其时间复杂性为O(log2n) • 在最坏的情况下,二叉排序树为近似线性表时(如以升序或降序输入结点时,见右图),其查找深度为n量级,即其时间复杂性为O(n) 9-30
92 88 80 75 64 5 13 19 21 37 56 9.3 动态查找表 • 二叉排序树特性 • 一个无序序列可以通过构造一棵二叉排序树而变成一个有序序列(通过中序遍历) • 插入新记录时,只需改变一个结点的指针,相当于在有序序列中插入一个记录而不需要移动其它记录 • 二叉排序树既拥有类似于折半查找的特性,又采用了链表作存储结构 • 但当插入记录的次序不当时(如升序或降序),则二叉排序树深度很深(见右图),增加了查找的时间 9-31
56 13 64 5 37 92 56 60 21 80 13 80 64 92 88 5 21 75 19 19 37 75 88 9.3 动态查找表 • 平衡二叉树 平衡二叉树(Balanced Binary Tree, Height-Balanced Tree,又称AVL树,Adelsen - Velskii and Landis,阿德尔森一维尔斯和兰迪斯)是二叉排序树的另一种形式。其特点为:树中每个结点的左、右子树深度之差的绝对值不大于1,即|hL-hR|≤1。可以证明,它们的深度和logn是同数量级的(其中n为节点个数)。 AVL树 非AVL树 9-32
56 0 13 -1 0 80 64 -1 1 92 5 0 21 0 19 0 37 0 75 0 88 0 9.3 动态查找表 • 平衡二叉树平衡因子 • 每个结点附加一个数字, 给出该结点左子树的高度减去右子树的高度所得的高度差,这个数字即为结点的平衡因子(balance factor) • AVL树任一结点平衡因子只能取 -1, 0, 1 • 若某个结点的平衡因子的绝对值大于1,则这棵二叉搜索树就失去了平衡,不再是AVL树。 9-33
9.3 动态查找表 • 非平衡二叉树的平衡处理 若一棵二叉排序树是平衡二叉树,扦入某个结点后,可能会变成非平衡二叉树,这时,就可以对该二叉树进行平衡处理,使其变成一棵平衡二叉树。 处理的原则应该是处理与插入点最近的、而平衡因子又比1大或比-1小的结点。下面将分四种情况讨论平衡处理。 9-34
2 C 1 0 B B 0 0 0 A A C 9.3 动态查找表 • LL型 的处理(左左型) • 如下图所示,若在C的左孩子B上扦入一个左孩子结点A,使C的平衡因子由1变成了2,成为不平衡的二叉树序树。 • 平衡处理:将C顺时针旋转,成为B的右子树,而原来B的右子树则变成C的左子树,待扦入结点A作为B的左子树。 结点旁边的数字表示该 结点的平衡因子 9-35
2 2 C C 1 0 -1 B B A 0 0 0 A A C 0 B 9.3 动态查找表 • LR型的处理(左右型) • 如下图所示,在C的左孩子A上扦入一个右孩子B,使的C的平衡因子由1变成了2,成为不平衡的二叉排序树。 • 平衡处理:将B变到A与C 之间,使之成为LL型,然后按第1种情形LL型处理。 结点旁边的数字表示该 结点的平衡因子 9-36
-2 C 0 -1 B B 0 0 0 A A C 9.3 动态查找表 • RR型的处理(右右型) • 如下图所示,在A的右孩子B上扦入一个右孩子C,使A的平衡因子由-1变成-2,成为不平衡的二叉排序树。 • 平衡处理:将A逆时针旋转,成为B的左子树,而原来B的左子树则变成A的右子树,待扦入结点C成为B的右子树。 结点旁边的数字表示该 结点的平衡因子 9-37
-2 A -2 C 1 0 C -1 B B 0 0 B 0 0 A A C 9.3 动态查找表 • RL型的处理(右左型) • 如下图所示,在A的右孩子C上扦入一个左孩子B,使A的平衡因子由-1变成-2,成为不平衡的二叉排序树。 • 平衡处理:将B变到A与C之间,使之成为RR型,然后按第(3) 种情形RR型处理。 结点旁边的数字表示该 结点的平衡因子 9-38
-2 4 -1 4 -1 5 RR型处理 0 5 0 0 7 0 0 4 0 5 4 7 c) 插入7,不平衡,要处理 • 插入4,平衡 b) 插入5,平衡 9.3 动态查找表 • 例1:给定一个关键字序列4,5,7,2 ,1,3,6,试生成一棵平衡二叉树。 • 分析:平衡二叉树实际上也是一棵二叉排序树,故可以按建立二叉排序树的思想建立,在建立的过程中,若遇到不平衡,则进行相应平衡处理,最后就可以建成一棵平衡二叉树。具体生成过程见下图。 9-39
2 5 2 0 4 1 7 1 5 0 2 LL型处理 1 5 0 0 0 4 7 0 1 0 2 7 2 0 e) 插入1,不平衡,要处理 1 0 4 d) 插入7,平衡 2 5 0 -1 0 4 2 LR型处理 0 7 -1 0 2 5 1 1 4 0 0 0 0 1 3 7 3 f) 插入3,不平衡,要处理 9.3 动态查找表 9-40
-1 4 0 0 -2 4 2 5 RL型处理 0 0 0 0 1 2 6 0 0 1 3 7 0 0 0 1 3 5 7 6 f) 插入6,不平衡,要处理 9.3 动态查找表 9-41
9.3 动态查找表 • 平衡二叉树的查找及性能分析 平衡二叉树本身就是一棵二叉排序树,故它的查找与二叉排序树完全相同。但它的查找 性能优于二叉排序树,不像二叉排序树一样,会出现最坏的时间复杂度O(n),它的时间复杂度与二叉排序树的最好时间复杂相同,都为O(log2n)。 9-42
4 4 2 6 2 5 1 3 5 7 1 3 7 6 平衡二叉树 二叉排序树 9.3 动态查找表 • 例2,对例1给定的关键字序列4,5,7,2,1,3,6,试用二叉排序树和平衡二叉树两种方法查找,给出查找6的次数及成功的平均查找长度。 • 分析:由于关键字序列的顺序己经确定,故得到的二叉排序树和平衡二叉树都是唯一的。得到的平衡二叉树见下座图,得到的二叉排序树见下右图。 从右图的二叉排序树可知,查找6需4次,平均查找长度 ASL=(1+2+2+3+3+3+4)/7=18/7≈2.57 从左图的平衡二叉树可知,查找6需2次,平均查找长度 ASL=(1+2+2+3+3+3+3)=17/7≈2.43 从结果可知,平衡二叉树的查找性能优于二叉排序树。 9-43
9.4 哈希表 9.4.1哈希表 哈希(Hash)表又称散列表,是一种直接计算记录存放地址的方法,它在关键码与存储位置之间直接建立了映象。 • 哈希函数 • 哈希函数是从关键字空间到存储地址空间的一种映象 • 哈希函数在记录的关键字与记录的存储地址之间建立起一种对应关系。可写成: addr(ai)= H(keyi) • H( )为哈希函数 • keyi是表中元素ai关键字,addr(ai)是存储地址 9-44
9.4 哈希表 • 哈希查找 • 哈希查找也叫散列查找,是利用哈希函数进行查找的过程 • 首先利用哈希函数及记录的关键字计算出记录的存储地址 • 然后直接到指定地址进行查找 • 不需要经过比较,一次存取就能得到所查元素 • 哈希冲突 • 不同的记录,其关键字通过哈希函数的计算,可能得到相同的地址 • 把不同的记录映射到同一个散列地址上,这种现象称为冲突 9-45
9.4 哈希表 • 哈希表定义 • 根据设定的哈希函数 H(key) 和所选中的处理冲突的方法 • 将一组关键字映象到一个有限的、地址连续的地址集上 • 以关键字在地址集中的“象”作为相应记录在表中的存储位置 • 如此构造所得的查找表称之为“哈希表”。 • 哈希函数均匀性 • 哈希函数实现的一般是从一个大的集合(部分元素,空间位置上一般不连续)到一个小的集合(空间连续)的映射 • 一个好的哈希函数,对于记录中的任何关键字,将其映射到地址集合中任何一个地址的概率应该是相等的 • 即关键字经过哈希函数得到一个“随机的地址” 9-46
9.4 哈希表 9.4.2 哈希函数 • 要求 • 哈希函数应是简单的,能在较短的时间内计算出结果 • 哈希函数的定义域必须包括需要存储的全部关键字,如果散列表允许有 m 个地址时,其值域必须在 0 到 m-1 之间 • 散列函数计算出来的地址应能均匀分布在整个地址空间中 9-47
9.4 哈希表 • 哈希函数(直接定址法) • 直接定址法中,哈希函数取关键字的线性函数 H(key) = a ×key + b 其中a和b为常数 • 直接定址法举例 H(key) = key - 2005131000 9-48
9.4 哈希表 • 直接定址法特性 • 直接定址法仅适合于地址集合的大小与关键字集合的大小相等的情况 • 当a=1时,H(key)=key,即用关键字作地址 • 在实际应用中能使用这种哈希函数的情况很少 9-49
9.4 哈希表 • 哈希函数(数字分析法) • 假设关键字集合中的每个关键字都是由 s 位数字组成 (u1, u2, …, us) • 分析关键字集中的全体 • 从中提取分布均匀的若干位或它们的组合作为地址。 9-50