1、第8章 查 找,第8章 查 找,8.1 基本概念 8.2 静态查找表8.3 动态查找 8.4 散列表 8.4 应用举例及分析习题,8.1 基 本 概 念,一、定义查找表(search table):由同一类型的数据元素或记录构成的集合,每个数据元素由若干数据项组成。关键字(key):能够标识数据元素(或记录)的一个或几个数据项称为关键字,能够唯一标识一个数据元素的关键字称为主关键字(Primarykey);若能标识若干个数据元素的关键字称为次关键字(Secondary key)。,查找:根据给定的值,在查找表中查找是否存在关键字等于给定值的记录。若找到,则查找成功,查找结果可以是对应记录在查找
2、表中的位置或数据元素的值;或找不到,则查找不成功,查找结果可以给出一个操作标志或“空”指针。 内部查找与外部查找:若查找过程在内存中进行称之为内部查找,若查找过程需要访问外存,则称为外部查找。,二、在计算机中,查找主要分为以下几类:,静态查找:是指查询某个“特定”数据元素是否在查找表中(顺序查找、二分查找、索引表查找);动态查找:是指在查找过程中同时插入查找表中不存在的元素,或从表中删除已存在的某个数据元素,或从表中删除已存在的某个数据元素(二叉树查找)。哈希查找:利用哈希函数,通过计算求得待查元素的存储地址的过程。,三、查找算法的衡量(散列表及查找):,l对于查找算法,把对关键字的最多比较次
3、数称作最大查找长度(MSL:maximum search length)。l平均比较次数称做平均查找长度(ASL:Average search length)。常把MSL和MSL和ASL作为基本的技术指标加以考查。,8.2 静态查找表,静态查找表主要有三种结构: 顺序表 有序顺序表 索引顺序表针对静态查找表的查找算法主要有: 顺序查找(线性查找) 折半查找(二分查找) 分块查找(索引顺序查找),8.2.1 顺序表上顺序查找 查找思想: 从表的一端开始,顺序扫描查找表,若关键字值与K相等,则查找成功,反回记录在表中的位置值,若查找不成功,则返回值为0。,存储:数据元素采用顺序表或线性链表。 数据
4、存储说明: #define MAXSIZE 100 #define KEYTYPE int typedef struct keytype key; otherdata; SSELEMENT; typedef stnct SSELEMENT rMAXSIZE int len; SSTABLE;,8.2.2 有序表查找二分查找(折半查找):当静态表中记录的关键字有序时,并且是按顺序存储结构存储的,则可用二分查找法,可提高查找效率。查找思想: 用Low和high分别表示有序表的记录下界和上界,中间记录位置即为mid= (low+high)/2 当: kst.rmid.key 时,low=mid+1,
5、继续查找.当下界(low)大于上界(high)时,查找失败并结束。,运算步骤:(1) low =0,high =10 ,故mid =5 ,待查范围是 0,10;(2) 若 S.listmid.key key,说明keylow ,mid-1, 则令:high =mid1;重算 mid ;(4)若 S.list mid .key= = key,说明查找成功,元素序号=mid;结束条件:(1)查找成功 : S.listmid.key = key(2)查找不成功 : highlow (意即区间长度小于0),解:设定3个辅助标志: low,high,mid,有:mid= (low+high)/2,折半查
6、找举例:,Low指向待查元素所在区间的下界,high指向待查元素所在区间的上界,mid指向待查元素所在区间的中间位置,已知如下11个元素的有序表:(05 13 19 21 37 56 64 75 80 88 92), 请查找关键字为21 的数据元素。,(2)算法的实现,int BinarySearch(SeqList S, DataType x)int low = 0, high = S.size-1;/确定初始查找区间上下界 int mid; while(low x.key) high = mid - 1;return -1;/查找失败,算法效率分析: 二分查找的比较次数不会超过二叉树的深度
7、d= log2n +1,可见,二分查找的优点是速度快,但前提是记录的关键字必须有序,且为向量(顺序存储)存储结构。,也称分块查找,它是以索引顺序表表示的静态查找表,此方法在查找表上需建立一个索引表,索引表由若干表项构成,每个表项包括两部分内容。,关键字项指 针 项,8.2.3索引顺序表查找,思路:先让数据分块有序,即分成若干子表,要求每个子表中的数据元素值都比后一块中的数值小(但子表内部未必有序)。然后将各子表中的最大关键字构成一个索引表,表中还要包含每个子表的起始地址(即头指针)。,例:,特点:块间有序,块内无序,分块查找过程举例:,索引表,块内最大关键字,各块起始地址,第1块,第2块,第3
8、块,特点:块间有序,块内无序,查找步骤分两步进行:, 对索引表使用折半查找法(因为索引表是有序表); 确定了待查关键字所在的子表后,在子表内采用顺序查找法(因为各子表内部是无序表);,查找:块间折半,块内线性,8.3 动 态 查 找,动态查找对表中的记录要经常进行插入和删除操作,动态查找的这种特性要求采用灵活的存储方法来组织查找表中的记录,重点介绍二叉排序树查找.二叉排序树的概念: 二叉排序树是一种特殊的二叉树,具有下列特点:(1)若它的左子树不空,则左子树上所有结点的关键字均小于它的根结点的关键字;(2) 若它的右子树不空,则右子树上的所有结点的关键字均大于它的根结点的关键字;(3) 它的左
9、子要、右子树分别也是二叉排序树。若构造了一棵二叉排序树,则其中根遍历的序列是按结点关键字递增排序的有序序列。,二叉排序树存储结构(Binary sort tree)采用二叉链表作为存储结构,结点类型如下:#define KEYTYPE inttypedef struct nodeKEYTYPE key;otherdatastruct node *lchile,*rchile;BSTNODE;,8.3.1二叉排序树的生成和插入二叉排序树生成的形式算法描述为: 对于一组关键字结点序列 (1)第一个结点做为二叉排序树的要根结点; (2)从第二个结点起,将读入结点的关键字和根结点的关键字进行比较: 读
10、入结点的关键字等于根结点的关键字,则树中已有此结点,不作处理; 读入结点的关键字大于根结点的关键字,则此结点插入到根结点的右子树中; 读入结点的关键字小于根结点的关键字,则此结点插入到根结点的左了树中; 子树中的插入过程与1.2.相同。,算法如下:insert_bst(KEYTYPE k, BSTNODE *p) if(p=NULL) p=malloc(sizeof(BSTNODE); p-lchild=NULL; p-rchild=NULL; p-key=k; else if(kp-key) insert_bst(k,p-rchild);else insert_bst(k,p-lchile)
11、; ,8.3.2二叉排序树上的查找 二叉排序树上查找的形式算法描述为: 将给定值与二叉排序树的根结点的关键字比较:(1)给定值等于根结点的关键字,则根结点就是要查找的结点;(2)给定值大于根结点的关键字,则继续在根结点的右子树中查找;(3)给定值小于根结点的关键字,则继续在根结点的左子树中查找;(4)在子树中的查找过程和(1)(2)(3)相同。,45,如果改变输入顺序为(24,53, 45,45,12,24,90),,例:输入待查找的关键字序列=(45,24,53,45,12,24,90),查找不成功则插入树中,则生成的二叉排序树形态不同,24,这种既查找又插入的过程称为动态查找,二叉排序树上
12、的查找算法:BSTNODE *search_node(KEYTYPE k,BSTNODE *r) BSTNODE *p; if(r=NULL) p=NULL; else if(k=r-key) p=r;else if(kr-key) p=search_nede(k,r_lchild);return p;,查找算法分析: 二叉排序树上进行的查找,查找过程中与结点关键字比较的次数至多不超过二叉排序树的深度,前图的平均查找长度(1+2+2+3+3+3+4+4+4+4)/11=30/11 当二叉排序树的形态和二分查找的判定树一样时,它的平均查找长度和为Log2n成正比,当二叉排序树的形态蜕变为单分支树
13、时,平均查找长度与顺序查找一样,变为(n+1)/2.,8.3.3 二叉排序树的删除,l 从二叉排序树中删除一个结点后,要保证删除后所得的二叉树仍是一棵二叉排序树。l 删除操作首先进行查找,确定被删除结点是否在二叉排序树中;然后根据被删结点的情况分别进行不同的删除操作:l 被删结点为P指针所指,双亲结点用f指针所指,被删除结点的左右子树用PL和PR表示,则删除结点的操作可有如下的表达:,(1)若删除结点是叶子结点,则只需修改被删结点的双亲结点的指针即可,如F-lchild=NULL;(2) 若被删结点只有左子树PL或只有右子树PR,只要令PL或PR直接成为其双亲结点的左子树或右子树即可;(3)
14、若被删除结点的左子树和右子树均不空时,在删除该结点前为了保持其余结点之间的序列位置相对不变,先要用被删除结点在该树的中序遍历序列中的直接前驱结点的值取代被删除结点的值,然后再从二叉排序树中删除那个直接前驱结点。,84 散 列 表,841 散列表与散列函数,散列:由关键字通过确定的对应关系计算得到记录的存储位置的过程称散列。哈希表:按散列形式的记录的集合称散列表或哈希表。所使用的对应关系称散列函数或哈希函数散列查找:也称哈希查找,是由关键字通过计算得到记录存储地址的查找方法。 冲 突:通常关键码的集合比哈希地址集合大得多,因而经过哈希函数变换后,可能将不同的关键码映射到同一个哈希地址上,这种现象
15、称为冲突。,例:有数据元素序列(14,23,39,9,25,11),若规定每个元素k的存储地址H(k)k,请画出存储结构图。,解:根据散列函数H(k)k ,可知元素14应当存入地址为14的单元,元素23应当存入地址为23的单元,对应散列存储表(哈希表)如下:,根据存储时用到的散列函数H(k)表达式,即可查到结果!例如,查找key=9,则访问H(9)=9号地址,若内容为9则成功;若查不到,应当设法返回一个特殊值,例如空指针或空记录。,14,11,9,23,25,39,明显缺点:空间效率低,有6个元素的关键码分别为:(14,23,39,9,25,11)。选取关键码与元素位置间的函数为H(k)=k
16、mod 7,通过哈希函数对6个元素建立哈希表:,25,39,23,9,14,冲突现象举例:,6个元素用7个地址应该足够!,H(14)=14%7=0,11,H(25)=25%7=4H(11)=11%7=4,有冲突!,所以,哈希方法必须解决以下两个问题:,1)构造好的哈希函数(a)所选函数尽可能简单,以便提高转换速度;(b)所选函数对关键码计算出的地址,应在哈希地址内集中并大致均匀分布,以减少空间浪费。,2)制定一个好的解决冲突的方案 查找时,如果从哈希函数计算出的地址中查不到关键码,则应当依据解决冲突的规则,有规律地查询其它相关单元。,在哈希查找方法中,冲突是不可能避免的,只能尽可能减少。,常用
17、的哈希函数构造方法有:,直接定址法 数字分析法除留余数法,要求一:n个数据原仅占用n个地址,虽然散列查找是以空间换时间,但仍希望散列的地址空间尽量小。要求二:无论用什么方法存储,目的都是尽量均匀地存放元素,以避免冲突。,842散列函数的构造方法,Hash(key) = akey + b (a、b为常数)优点:以关键码key的某个线性函数值为哈希地址,不会产生冲突.缺点:要占用连续地址空间,空间效率低。,例:关键码集合为100,300,500,700,800,900, 选取哈希函数为Hash(key)=key/100, 则存储结构(哈希表)如下:,1、直接定址法,特点:选用关键字的某几位组合成哈
18、希地址。选用原则应当是:各种符号在该位上出现的频率大致相同。,3 4 7 0 5 2 4 3 4 9 1 4 8 73 4 8 2 6 9 63 4 8 5 2 7 03 4 8 6 3 0 53 4 9 8 0 5 83 4 7 9 6 7 13 4 7 3 9 1 9,例:有一组(例如80个)关键码,其样式如下:,2、数字分析法,讨论: 第1、2位均是“3和4”,第3位也只有“ 7、8、9”,因此,这几位不能用,余下四位分布较均匀,可作为哈希地址选用。,位号: , 若哈希地址取两位(因元素仅80个),则可取这四位中的任意两位组合成哈希地址,也可以取其中两位与其它两位叠加求和后,取低两位作哈
19、希地址。,Hash(key)=key mod m (m是一个整数)特点:以关键码除以p的余数作为哈希地址。关键:如何选取合适的m技巧:若设计的哈希表长为m,则一般取pm且为质数 (也可以是合数,但不能包含小于20的质因子)。,3、除留余数法, 执行速度(即计算哈希函数所需时间); 关键字的长度; 哈希表的大小; 关键字的分布情况; 查找频率。,小结:构造哈希函数的原则:,8.4.3 哈希冲突解决方法,常见的冲突处理方法有:,开放定址法(开地址法) 链地址法(拉链法),1、开散列表处理冲突(开链地址法),基本思想:将具有相同哈希地址的记录链成一个单链表,m个哈希地址就设m个单链表,然后用一个数组
20、将m个单链表的表头指针存储起来,形成一个动态的结构。,设 47, 7, 29, 11, 16, 92, 22, 8, 3, 50, 37, 89 的哈希函数为:Hash(key)=key mod 11,用开链地址法处理冲突,则建表如右图所示。,例:,有冲突的元素可以插在表尾,也可以插在表头。,2.闭散列表处理冲突(开放定址法),设计思路:有冲突时就去寻找下一个空的哈希地址,只要哈希表足够大,空的哈希地址总能找到,并将数据元素存入。,具体实现:,Hi=(Hash(k)+di) mod m ( 1i m ) 其中: Hash(k)为哈希函数 m为哈希表长度 di 为增量序列 1,2,m-1,且di
21、=i,(1)线性探测法,含义:一旦冲突,就找附近(下一个)空地址存入。,关键码集为 47,7,29,11,16,92,22,8,3,设:哈希表表长为m=11; 哈希函数为Hash(key)=key mod 11; 拟用线性探测法处理冲突。建哈希表如下:,解释: 47、7是由哈希函数得到的没有冲突的哈希地址;,0 1 2 3 4 5 6 7 8 9 10, ,例:,29,22,8,3, Hash(29)=7,哈希地址有冲突,需寻找下一个空的哈希地址:由H1=(Hash(29)+1) mod 11=8,哈希地址8为空,因此将29存入。, 另外,22、8、3同样在哈希地址上有冲突,也是由H1找到空的
22、哈希地址的。,其中3 还连续移动了两次(二次聚集),线性探测法的优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素;线性探测法的缺点:可能使第i个哈希地址的同义词存入第i+1个哈希地址,这样本应存入第i+1个哈希地址的元素变成了第i+2个哈希地址的同义词, 因此,可能出现很多元素在相邻的哈希地址上“堆积”起来,大大降低了查找效率。解决方案:可采用二次探测法或伪随机探测法,以改善“堆积”问题。,仍举上例,改用二次探测法处理冲突,建表如下:,0 1 2 3 4 5 6 7 8 9 10, ,注:只有3这个关键码的冲突处理与上例不同,Hash(3)=3,哈希地址上冲突,由H1=(Ha
23、sh(3)+12) mod 11=4,仍然冲突;H2=(Hash(3)-12) mod 11=2,找到空的哈希地址,存入。,2. 二次探测法,Hi=(Hash(k)di) mod m其中:Hash(k)为哈希函数 m为哈希表长度,m要求是某个4k+3的质数; di为增量序列 12,-12,22,-22,q2,8.4.4 散列表的查找与分析,一开散列表上的查找CHAINHASH *chain-hash-search(KEYTYPE k,CHAINHASH *htc) CHAINHASH *p;p=htch(k);while(p!=NULL ,二 闭散列表上的查找int seq-hash-sear
24、ch(KEYTYPE k,HASHTABLE *htl,int m) int d ,I=0; d=h(k);while(Im ,8.5 应用举例及分析,例1 画出对长度为10 的有序表进行二分查找的判定树,并求其等概率时查找成功的平均查找长度。10个有序整数的序列为(10,20,30,40,50,60,70,80,90,100)。对应的二分查找的判定树如下图8所示。判定树共四层,第一层表示比较一次可查到的结点有一个,第二层表示比较两次可查到的结点有两个,第三层表示比较三次可查到的结点有四个,第四层表示比较四次可查到的结点有三个。计算等概率时查找成功的平均查找长度为(1+2+2+3+3+3+3+
25、4+4+4)/10=29/10。,例2:已知一长度为12的关键字序列Jan,Feb,Mar,Apr,May,Jun,Jul, Aug,Sep,Oct,Nov,Dec试按表中元素的次序建立一棵二叉排序树,画出此二叉排序树,并求在等概率情况下查找成功的平均查找长度。对上面的二叉排序树进行中序遍历,获得一有序表,求在等概率情况下对此有序表进行二分查找时查找成功的平均查找长度。 此题中的关键字是字符串,比较关键字需按英文字母在ASCII码表中的编码大小进行比较。以此原则建立的二叉排序树如下图所示。计算等概率情况下查找成功的平均查找长度。将查找每个记录需要比较关键字的次数相加除以记录个数即得,为(1+2+2+3+3+3+4+4+4+5+5+6)/12=42/12。,对上面的二叉排序树中进行中序遍历,获得的有序表为(Apr,Aug,Dec,Feb,Jan,Jun,Jul,Mar,Nov,Oct,Sep),对此有序表进行二分查找时,求在等概率情况下查找成功的平均查找长度,其方法与上题中描述的方法一样,为(1+2+2+3+3+3+3+4+4+4+4+4)/12=37/12。,习题,P151 8.2, 8.4, 8.5,