《静态索引结构动态索引结构散列可扩充散列.ppt》由会员分享,可在线阅读,更多相关《静态索引结构动态索引结构散列可扩充散列.ppt(161页珍藏版)》请在三一办公上搜索。
1、静态索引结构动态索引结构散列可扩充散列,第十章 索引与散列,静态索引结构,示例:有一个存放职工信息的数据表,每一 个职工对象有近 1k 字节的信息,正好占据一 个页块的存储空间。,当数据对象个数 n 很大时,如果用无序表形式的静态搜索结构存储,采用顺序搜索,则搜索效率极低。如果采用有序表存储形式的静态搜索结构,则插入新记录进行排序,时间开销也很可观。这时可采用索引方法来实现存储和搜索。,线性索引(Linear Index List),100140180220260300340380,key addr 03 180 08 140 17 340 24 260 47 300 51 380 83 10
2、0 95 220,职工号 姓名 性别 职务 婚否 83 张珊 女 教师 已婚 08 李斯 男 教师 已婚.03 王鲁 男 教务员 已婚.95 刘琪 女 实验员 未婚.24 岳跋 男 教师 已婚.47 周斌 男 教师 已婚.17 胡江 男 实验员 未婚.51 林青 女 教师 未婚.,索引表,数据表,假设内存工作区仅能容纳 64k 字节的数据,在某一时刻内存最多可容纳 64 个对象以供搜索。如果对象总数有 14400 个,不可能把所有对象的数据一次都读入内存。无论是顺序搜索或折半搜索,都需要多次读取外存记录。如果在索引表中每一个索引项占4个字节,每个索引项索引一个职工对象,则 14400 个索引项
3、需要 56.25k 字节,在内存中可以容纳所有的索引项。,这样只需从外存中把索引表读入内存,经过搜索索引后确定了职工对象的存储地址,再经过 1 次读取对象操作就可以完成搜索。稠密索引:一个索引项对应数据表中一个对象的索引结构。当对象在外存中按加入顺序存放而不是按关键码有序存放时必须采用稠密索引结构,这时的索引结构叫做索引非顺序结构。稀疏索引:当对象在外存中有序存放时,可以把所有 n 个对象分为 b 个子表(块)存放,一个索引项对应数据表中一组对象(子表)。,在子表中,所有对象可能按关键码有序地存放,也可能无序地存放。但所有这些子表必须分块有序,后一个子表中所有对象的关键码均大于前一个子表中所有
4、对象的关键码。它们都存放在数据区中。另外建立一个索引表。索引表中每一表目叫做索引项,它记录了子表中最大关键码max _key以及该子表在数据区中的起始位置obj _ addr。第 i 个索引项是第 i 个子表的索引项,i=0,1,n-1。这样的索引结构叫做索引顺序结构。,对索引顺序结构进行搜索时,一般分为两级搜索:先在索引表 ID 中搜索给定值 K,确定满足 IDi-1.max_key K IDi.max_key,22 12 13 30 29 33,36 42 44 48 39 40,60 74 56 79 80 66,92 82 88 98 94,子表1,子表2,子表3,子表4,数据区,33
5、488098,索引表,1234,max_ max_ key addr,的 i 值,即待查对象可能在的子表的序号。然后再在第 i 个子表中按给定值搜索要求的对象。索引表是按max_key有序的,且长度也不大,可以折半搜索,也可以顺序搜索。各子表内各个对象如果也按对象关键码有序,可以采用折半搜索或顺序搜索;如果不是按对象关键码有序,只能顺序搜索。,索引顺序搜索的搜索成功时的平均搜索长度 ASLIndexSeq=ASLIndex+ASLSubList其中,ASLIndex 是在索引表中搜索子表位置的平均搜索长度,ASLSubList 是在子表内搜索对象位置的搜索成功的平均搜索长度。设把长度为 n 的
6、表分成均等的 b 个子表,每个子表 s 个对象,则 b=n/s。又设表中每个对象的搜索概率相等,则每个子表的搜索概率为1/b,子表内各对象的搜索概率为 1/s。若对索引表和子表都用顺序搜索,则索引顺序搜索的搜索成功时的平均搜索长度为 ASLIndexSeq=(b+1)/2+(s+1)/2=(b+s)/2+1,索引顺序搜索的平均搜索长度与表中的对象个数 n 有关,与每个子表中的对象个数 s 有关。在给定 n 的情况下,s 应选择多大?用数学方法可导出,当 s=时,ASLIndexSeq取极小值+1。这个值比顺序搜索强,但比折半搜索差。但如果子表存放在外存时,还要受到页块大小的制约。若采用折半搜索
7、确定对象所在的子表,则搜索成功时的平均搜索长度为 ASLIndexSeq=ASLIndex+ASLSubList log2(b+1)-1+(s+1)/2 log2(1+n/s)+s/2,倒排表(Inverted Index List),对包含有大量数据对象的数据表或文件进行搜索时,最常用的是针对对象的主关键码建立索引。主关键码可以唯一地标识该对象。用主关键码建立的索引叫做主索引。主索引的每个索引项给出对象的关键码和对象在表或文件中的存放地址。但在实际应用中有时需要针对其它属性进行搜索。例如,查询如下的职工信息:(1)列出所有教师的名单;,对象关键码 key 对象存放地址 addr,(2)已婚的
8、女性职工有哪些人?这些信息在数据表或文件中都存在,但都不是关键码,为回答以上问题,只能到表或文件中去顺序搜索,搜索效率极低。因此,除主关键码外,可以把一些经常搜索的属性设定为次关键码,并针对每一个作为次关键码的属性,建立次索引。在次索引中,列出该属性的所有取值,并对每一个取值建立有序链表,把所有具有相同属性值的对象按存放地址递增的顺序或按主关键码递增的顺序链接在一起。,次索引的索引项由次关键码、链表长度和链表本身等三部分组成。例如,为了回答上述的查询,我们可以分别建立“性别”、“婚否”和“职务”次索引。,性别次索引,次关键码 男 女 计 数 5 3 地址指针,指针 03 08 17 24 47
9、 51 83 95,婚否次索引,次关键码 已婚 未婚 计 数 5 3 地址指针,指针 03 08 24 47 83 17 51 95,职务次索引,次关键码 教师 教务员 实验员 计 数 5 1 2 地址指针,指针 08 24 47 51 83 03 17 95,(1)列出所有教师的名单;(2)已婚的女性职工有哪些人?通过顺序访问“职务”次索引中的“教师”链,可以回答上面的查询(1)。通过对“性别”和“婚否”次索引中的“女性”链和“已婚”链进行求“交”运算,就能够找到所有既是女性又已婚的职工对象,从而回答上面的查询(2)。倒排表是次索引的一种实现。在表中所有次关键码的链都保存在次索引中,仅通过搜
10、索次索引就能找到所有具有相同属性值的对象。,在次索引中记录对象存放位置的指针可以用主关键码表示:可通过搜索次索引确定该对象的主关键码,再通过搜索主索引确定对象的存放地址。在倒排表中各个属性链表的长度大小不一,管理比较困难。为此引入单元式倒排表。在单元式倒排表中,索引项中不存放对象的存储地址,存放该对象所在硬件区域的标识。硬件区域可以是磁盘柱面、磁道或一个页块,以一次 I/O 操作能存取的存储空间作为硬件区域为最好。,为使索引空间最小,在索引中标识这个硬件区域时可以使用一个能转换成地址的二进制数,整个次索引形成一个(二进制数的)位矩阵。例如,对于记录学生信息的文件,次索引可以是如图所示的结构。二
11、进位的值为 1 的硬件区域包含具有该次关键码的对象。如果在硬件区域1,中有要求的对象。然后将硬件区域1,等读入内存,在其中进行检索,确定就可取出所需对象。,单元式倒排表结构,m 路静态搜索树,当数据对象数目特别大,索引表本身也很大,在内存中放不下,需要分批多次读取外存才能把索引表搜索一遍。此时,可以建立索引的索引(二级索引)。二级索引可以常驻内存,二级索引中一个索引项对应一个索引块,登记该索引块的最大关键码及该索引块的存储地址。,如果二级索引在内存中也放不下,需要分为许多块多次从外存读入。可以建立二级索引的索引(三级索引)。这时,访问外存次数等于读入索引次数再加上1次读取对象。,02 06,1
12、1 15,18 23,29 32,38 41,45 49,52 60,77 95,(06,)(15,)(23,),(06,)(15,)(23,),(32,)(41,)(49,),(60,)(95,),(23,)(41,)(95,),root,head,必要时,还可以有4级索引,5极索引,。这种多级索引结构形成一种 m 叉树。树中每一个分支结点表示一个索引块,它最多存放 m 个索引项,每个索引项分别给出各子树结点(低一级索引块)的最大关键码和结点地址。树的叶结点中各索引项给出在数据表中存放的对象的关键码和存放地址。这种m叉树用来作为多级索引,就是m路搜索树。m路搜索树可能是静态索引结构,即结构在
13、初始创建,数据装入时就已经定型,在整个运行期间,树的结构不发生变化。,多级索引结构形成 m 路搜索树,m路搜索树还可能是动态索引结构,即在整个系统运行期间,树的结构随数据的增删及时调整,以保持最佳的搜索效率。,数据区,一级索引,二级索引,三级索引,四级索引,动态搜索结构,现在我们所讨论的 m 路搜索树多为可以动态调整的多路搜索树,它的一般定义为:一棵 m 路搜索树,它或者是一棵空树,或者是满足如下性质的树:根最多有 m 棵子树,并具有如下的结构:n,P0,(K1,P1),(K2,P2),(Kn,Pn)其中,Pi 是指向子树的指针,0 i n m;Ki 是关键码,1 i n m。Ki Ki+1,
14、1 i n。,动态的 m 路搜索树,在子树 Pi 中所有的关键码都小于 Ki+1,且大于 Ki,0 i n。在子树 Pn 中所有的关键码都大于Kn;在子树 P0 中的所有关键码都小于 K1。子树 Pi 也是 m 路搜索树,0 i n。,一棵3路搜索树的示例,35,20 40,a,b,c,d,e,25 30,10 15,45 50,m 路搜索树的类定义template class Mtree/基类 public:Triple,AVL树是2路搜索树。如果已知 m 路搜索树的度 m 和它的高度 h,则树中的最大结点数为,(等比级数前 h 项求和),每个结点中最多有 m-1 个关键码,在一棵高度为 h
15、 的 m 路搜索树中关键码的最大个数为 mh+1-1。高度 h=2 的二叉树,关键码最大个数为7;高度 h=3 的3路搜索树,关键码最大个数为 34-1=80。,struct Triple Mnode*r;/结点地址指针 int i;int tag;/结点中关键码序号 i;/tag=0,搜索成功;tag=1,搜索不成功,标识 m 路搜索树搜索结果的三元组表示,m 路搜索树上的搜索算法,在 m 路搜索树上的搜索过程是一个在结点内搜索和自根结点向下逐个结点搜索的交替的过程。,35,20 40,a,b,c,d,e,25 30,10 15,45 50,root,搜索35,template Triple
16、,q=p;p=p-ptri;/向下一层结点搜索 GetNode(p);/读入该结点 result.r=q;result.i=i;result.tag=1;return result;/搜索失败,返回插入位置,提高搜索树的路数 m,可以改善树的搜索性能。对于给定的关键码数 n,如果搜索树是平衡的,可以使 m 路搜索树的性能接近最佳。下面将讨论一种称之为B 树的平衡的 m 路搜索树。,B 树,一棵 m 阶B 树是一棵平衡的 m 路搜索树,它或者是空树,或者是满足下列性质的树:根结点至少有 2 个子女。除根结点以外的所有结点(不包括失败结点)至少有 m/2 个子女。所有的失败结点都位于同一层。在B
17、树中的“失败”结点是当搜索值x不在树中时才能到达的结点。事实上,在B 树的每个结点中还包含有一组指针Dm,指向实际对象的存放地址。,30,Ki与Di(1 i n m)形成一个索引项(Ki,Di),通过Ki可找到某个对象的存储地址Di。一棵B 树是平衡的 m 路搜索树,但一棵平衡的 m 路搜索树不一定是B 树。,35,20 40,25 30,10 15,45 50,root,45 50,35,40,20,root,10 15,25,非B 树 B 树,B 树的类定义和B 树结点类定义template class Btree:public Mtree/继承 m 路搜索树的所有属性和操作public:
18、int Insert(const Typetemplate class Mnode/B 树结点类定义private:,int n;/结点中关键码个数 Mnode*parent;/双亲指针 Type keym+1;/关键码数组 1m-1 Mnode*ptrm+1;/子树指针数组;,B 树的搜索算法,B 树的搜索算法继承了 m 路搜索树Mtree上的搜索算法。B 树的搜索过程是一个在结点内搜索和循某一条路径向下一层搜索交替进行的过程。,B 树的搜索时间与B 树的阶数 m 和B 树的高度h直接有关,必须加以权衡。在B 树上进行搜索,搜索成功所需的时间取决于关键码所在的层次;搜索不成功所需的时间取决于
19、树的高度。如果定义B 树的高度h 为失败结点所在的层次,需要了解树的高度h 与树中的关键码个数 N 之间的关系。如果让B 树每层结点个数达到最大,且设关键码总数为N,则树的高度达到最小:h=logm(N*(m-2)/(m-1)+1)-1,设在 m 阶B 树中每层结点个数达到最少,则B 树的高度可能达到最大。设树中关键码个数为 N,从B 树的定义知:0层 1 个结点 1层 至少 2 个结点 2层 至少 2 m/2 个结点 3层 至少 2 m/2 2 个结点 如此类推,h-1 层 至少有 2 m/2 h-2 个结点。所有这些结点都不是失败结点。,高度h与关键码个数N之间的关系,若树中关键码有N个,
20、则失败结点数为N+1。这是因为失败数据一般与已有关键码交错排列。因此,有 N+1=失败结点数=位于第 h 层的结点数 2 m/2 h-1 N 2 m/2 h-1-1 h-1 log m/2(N+1)/2)所有的非失败结点所在层次为0 h-1。示例:若B 树的阶数 m=199,关键码总数 N=1999999,则B 树的高度 h 不超过 log100 1000000+1=4,m值的选择,如果提高B 树的阶数 m,可以减少树的高度,从而减少读入结点的次数,因而可减少读磁盘的次数。事实上,m 受到内存可使用空间的限制。当 m 很大超出内存工作区容量时,结点不能一次读入到内存,增加了读盘次数,也增加了结
21、点内搜索的难度。m值的选择:应使得在B 树中找到关键码 x 的时间总量达到最小。这个时间由两部分组成:,从磁盘中读入结点所用时间在结点中搜索 x 所用时间根据定义,B 树的每个结点的大小都是固定的,结点内有 m-1 个索引项(Ki,Di,Pi),1 i m。其中,Ki 所占字节数为,Di 和 Pi 所占字节数为,则结点大小近似为 m(+2)个字节。读入一个结点所用时间为:tseek+tlatency+m(+2)ttran=a+bm,B 树的插入,B 树是从空树起,逐个插入关键码而生成的。在B 树,每个非失败结点的关键码个数都在 m/2-1,m-1 之间。插入在某个叶结点开始。如果在关键码插入后
22、结点中的关键码个数超出了上界 m-1,则结点需要“分裂”,否则可以直接插入。实现结点“分裂”的原则是:设结点 p 中已经有 m-1 个关键码,当再插入一个关键码后结点中的状态为,(m,P0,K1,P1,K2,P2,Km,Pm)其中 Ki Ki+1,1 i m这时必须把结点 p 分裂成两个结点 p 和 q,它们包含的信息分别为:结点 p:(m/2-1,P0,K1,P1,Km/2-1,Pm/2-1)结点 q:(m-m/2,Pm/2,Km/2+1,Pm/2+1,Km,Pm)位于中间的关键码 Km/2 与指向新结点 q 的指针形成一个二元组(Km/2,q),插入到这两个结点的双亲结点中去。,结点“分裂
23、”的示例,2 53 75,n P0 K1 P1 K2 P2,p,3 53 75 139,n P0 K1 P1 K2 P2 K3 P3,p,加入139,结点溢出,1 75,n P0 K1 P1,1 53,n P0 K1 P1,1 139,n P0 K1 P1,结点分裂,P,q,49 75,示例:从空树开始加入关键码建立3阶B 树,n=1 加入53,53,n=2 加入75,53 75,n=3 加入139,75,139,53,n=5 加入49,145,75,139 145,49 53,n=6 加入36,139 145,53,36,在插入新关键码时,需要自底向上分裂结点,最坏情况下从被插关键码所在叶结
24、点到根的路径上的所有结点都要分裂。,若设B 树的高度为h,那么在自顶向下搜索到叶结点的过程中需要进行 h 次读盘。,n=7 加入101,49,53,36,139,145,101,75,B 树的插入算法template int Btree:Insert(const Type/写出,/结点已满,分裂 int s=(m+1)/2;/求 m/2 insertkey(p,j,K,ap);/(K,ap)插入 j后 q=new Mnode;/建立新结点 move(p,q,s,m);/从 p向q 搬送 K=p-keys;ap=q;/分界码上移 if(p-parent!=NULL)/双亲不是根 t=p-pare
25、nt;GetNode(t);/读入双亲 j=0;t-key(t-n)+1=MAXKEY;while(t-keyj+1 parent=p-parent;PutNode(p);PutNode(q);,p=t;/继续 while(1)循环,向上调整 else/双亲是根 root=new Mnode;/创建新根 root-n=1;root-parent=NULL;root-key1=K;root-ptr0=p;root-ptr1=ap;q-parent=p-parent=root;PutNode(p);PutNode(q);PutNode(root);return 1;/跳出返回,当分裂一个非根的结点
26、时需要向磁盘写出 2 个结点,当分裂根结点时需要写出 3 个结点。如果我们所用的内存工作区足够大,使得在向下搜索时,读入的结点在插入后向上分裂时不必再从磁盘读入,那么,在完成一次插入操作时需要读写磁盘的次数=找插入结点向下读盘次数+分裂非根结点时写盘次数+分裂根结点时写盘次数=h+2(h-1)+3=3h+1。,B 树的删除,在B 树上删除一个关键码时,首先需要找到这个关键码所在的结点,从中删去这个关键码。若该结点不是叶结点,且被删关键码为 Ki,1 i n,则在删去该关键码之后,应以该结点 Pi 所指示子树中的最小关键码 x 来代替被删关键码 Ki 所在的位置,然后在 x 所在的叶结点中删除
27、x。在叶结点上的删除有 4 种情况。被删关键码所在叶结点同时又是根结点且删除前该结点中关键码个数 n 2,则直接删去该关键码并将修改后的结点写回磁盘。,被删关键码所在叶结点不是根结点且删除前该结点中关键码个数 n m/2,则直接删去该关键码并将修改后的结点写回磁盘,删除结束。被删关键码所在叶结点删除前关键码个数 n=m/2-1,若这时与该结点相邻的右兄弟(或左兄弟)结点的关键码个数 n m/2,则可按以下步骤调整该结点、右兄弟(或左兄弟)结点以及其双亲结点,以达到新的平衡。,36 49,m=3,删除36,49,55 58,删除55,简单删除,75 80,m=3,删除55,10,40,65,60
28、 70,30,50,a,c,b,d,e,f,g,h,58,75 80,10,40,65,60 70,30,50,a,c,b,d,e,f,g,h,将双亲结点中刚刚大于(或小于)该被删关键码的关键码 Ki(1 i n)下移;将右兄弟(或左兄弟)结点中的最小(或最大)关键码上移到双亲结点的 Ki 位置;将右兄弟(或左兄弟)结点中的最左(或最右)子树指针平移到被删关键码所在结点中最后(或最前)子树指针位置;在右兄弟(或左兄弟)结点中,将被移走的关键码和指针位置用剩余的关键码和指针填补、调整。再将结点中的关键码个数减1。,结点联合调整,55 58,75 80,m=3,删除65,10,40,65,60 7
29、0,30,50,a,c,b,d,e,f,g,h,55 58,80,10,40,70,60 75,30,50,a,c,b,d,e,f,g,h,调整g,c,h,删除65,70,删除70,55 58,80,m=3,删除70,10,40,60 75,30,50,a,c,b,d,e,f,g,h,55,80,10,40,60,58 75,30,50,a,c,b,d,e,f,g,h,调整f,c,g,结点联合调整,被删关键码所在叶结点删除前关键码个数 n=m/2-1,若这时与该结点相邻的右兄弟(或左兄弟)结点的关键码个数 n=m/2-1,则必须按以下步骤合并这两个结点。将双亲结点 p 中相应关键码下移到选定保
30、留的结点中。若要合并 p 中的子树指针 Pi 与 Pi+1 所指的结点,且保留 Pi 所指结点,则把 p 中的关键码 Ki+1下移到 Pi 所指的结点中。把 p 中子树指针 Pi+1 所指结点中的全部指针和关键码都照搬到 Pi 所指结点的后面。删去 Pi+1 所指的结点。,在结点 p 中用后面剩余的关键码和指针填补关键码 Ki+1 和指针 Pi+1。修改结点 p 和选定保留结点的关键码个数。在合并结点的过程中,双亲结点中的关键码个数减少了。若双亲结点是根结点且结点关键码个数减到 0,则将该双亲结点删去,合并后保留的结点成为新的根结点;否则将双亲结点与合并后保留的结点都写回磁盘,删除处理结束。若
31、双亲结点不是根结点且关键码个数减到m/2-2,又要与它自己的兄弟结点合并,重复上面的合并步骤。最坏情况下这种结点合并处理要自下向上直到根结点。,55,删除55,结点合并,80,m=3,删除55,10,40,60,58 75,30,50,a,c,b,d,e,f,g,h,合并f,g,58 60,80,10,40,75,30,50,a,c,b,d,e,f,h,80,55,删除80,结点合并,m=3,删除80,10,40,60,58 75,30,50,a,c,b,d,e,f,g,h,合并g,h,60 75,55,10,40,58,30,50,a,c,b,d,e,f,h,55,非叶结点删除,删除50,删
32、除55,55 58,75 80,m=3,删除50,10,40,65,60 70,30,50,a,c,b,d,e,f,g,h,58,75 80,删除55,10,40,65,60 70,30,a,c,b,d,e,f,g,h,用55取代,用58取代,58,75 80,10,40,65,60 70,30,a,c,b,d,e,f,g,h,合并f,g,58,75 80,10,40,60 65,70,30,a,c,b,d,e,f,h,结点合并与调整,删除70,58,80,10,40,60 65,75,30,a,c,b,d,e,f,h,删除70,用75取代,删除75,58,10,40,60 65,80,30,
33、a,c,b,d,e,f,h,删除75,用80取代调整f,c,h,58,80,10,40,60,65,30,a,c,b,d,e,f,h,删除10,80,30 40,60,f,h,58 65,d,b,B 树的关键码删除算法template int Btree:Delete(const Type/在叶结点删除,p=q;/转化为叶结点的删除 else compress(p,j);/叶结点,直接删除 int d=(m+1)/2;/求 m/2 while(1)/调整或合并 if(p-n parent;/找到双亲 GetNode(q);while(j n,p=q;/向上调整 if(p=root)break;
34、else break;if(!root-n)/调整后根的n减到0 p=root-ptr0;delete root;root=p;/删根 root-parent=NULL;/新根,B+树,一棵 m 阶B+树可以定义如下:树中每个非叶结点最多有 m 棵子树;根结点(非叶结点)至少有 2 棵子树。除根结点外,其它的非叶结点至少有 m/2 棵子树;有 n 棵子树的非叶结点有 n-1 个关键码。所有叶结点都处于同一层次上,包含了全部关键码及指向相应数据对象存放地址的指针,且叶结点本身按关键码从小到大顺序链接;,叶结点的子树棵数 n 可以多于 m,可以少于 m,视关键码字节数及对象地址指针字节数而定。若设
35、结点可容纳最大关键码数为 m1,则指向对象的地址指针也有 m1 个。,10 15,18 20 22,23 31,33 45,48 52,18 23,33,33,叶结点中的子树棵数 n 应满足 n m1/2,m1。若根结点同时又是叶结点,则结点格式同叶结点。所有的非叶结点可以看成是索引部分,结点中关键码 Ki 与指向子树的指针 Pi 构成对子树(即下一层索引块)的索引项(Ki,Pi),Ki 是子树中最小的关键码。特别地,子树指针 P0 所指子树上所有关键码均小于 K1。结点格式同B 树。,叶结点中存放的是对实际数据对象的索引。,在B+树中有两个头指针:一个指向 B+树的根结点,一个指向关键码最小
36、的叶结点。可对B+树进行两种搜索运算:循叶结点链顺序搜索另一种是从根结点开始,进行自顶向下,直至叶结点的随机搜索。在B+树上进行随机搜索、插入和删除的过程基本上与B 树类似。只是在搜索过程中,如果非叶结点上的关键码等于给定值,搜索并不停止,而是继续沿右指针向下,一直查到叶结点上的这个关键码。,B+树的搜索分析类似于B 树。B+树的插入仅在叶结点上进行。每插入一 个(关键码-指针)索引项后都要判断结点中的子树棵数是否超出范围。当插入后结点中的子树棵数 n m1 时,需要将叶结点分裂为两个结点,它们的关键码分别为(m1+1)/2 和(m1+1)/2。它们的双亲结点中应同时包含这两个结点的最小关键码
37、和结点地址。此后,问题归于在非叶结点中的插入了。,3个关键码的B+树,10 12 23,加入关键码33,结点分裂,10 12,23 33,23,10 12,18 20 22,23 33,18 23,加入更多的关键码,在非叶结点中关键码的插入与叶结点的插入类似,但非叶结点中的子树棵数的上限为 m,超出这个范围就需要进行结点分裂。在做根结点分裂时,因为没有双亲结点,就必须创建新的双亲结点,作为树的新根。这样树的高度就增加一层了。B+树的删除仅在叶结点上进行。当在叶结点上删除一个(关键码-指针)索引项后,结点中的子树棵数仍然不少于 m1/2,这属于简单删除,其上层索引可以不改变。,如果删除的关键码是
38、该结点的最小关键码,但因在其上层的副本只是起了一个引导搜索的“分界关键码”的作用,所以上层的副本仍然可以保留。如果在叶结点中删除一个(关键码-指针)索引项后,该结点中的子树棵数 n 小于结点子树棵数的下限 m1/2,必须做结点的调整或合并工作。,删除18,10 15,18 20 22,23 31,33 45,48 52,18 23,33,33,10 15,20 22,23 31,33 45,48 52,18 23,33,33,删除关键码18上层索引不改,10 15,18 20 22,23 31,33 45,48 52,18 23,33,33,15 18,20 22,23 31,33 45,48
39、 52,20 23,33,33,删除关键码10,18移入结点,索引改20,删除10,如果右兄弟结点的子树棵数已达到下限m1/2,没有多余的关键码可以移入被删关键码所在的结点,必须进行两个结点的合并。将右兄弟结点中的所有(关键码-指针)索引项移入被删关键码所在结点,再将右兄弟结点删去。结点的合并将导致双亲结点中“分界关键码”的减少,有可能减到非叶结点中子树棵数的下限 m/2 以下。这样将引起非叶结点的调整或合并。如果根结点的最后两个子女结点合并,树的层数就会减少一层。,10 15,18 20 22,23 31,33 45,48 52,18 23,33,33,15 18,20 22,23 31,2
40、0 23 45,删除关键码33,右边两结点合并,影响到非叶结点合并,可能直到根结点,导致高度减少,删除33,45 48 52,散列(Hashing),在计算科学中把词典当作一种抽象数据类型。在讨论词典抽象数据类型时,把词典定义为 对的集合。,在现实中,经常遇到按给定的值进行查询的事例。为此,必须在开发相应软件时考虑在记录的存放位置和用以标识它的数据项(称为关键码)之间的对应关系,从而选择适当的数据结构,很方便地根据记录的关键码检索到对应记录的信息。,词典(Dictionary)的抽象数据类型,根据问题的不同,可以为名字和属性赋予不同的含义。例如,在图书馆检索目录中,名字是书名,属性是索书号及作
41、者等信息;在计算机活动文件表中,名字是文件名,属性是文件地址、大小等信息。,词典的抽象数据类型const int DefaultSize=26;templateclass Dictionary public:,Dictionary(int size=DefaultSize);int IsIn(Name name);Attribute*Find(Name name);void Insert(Name name,Attribute attr);void Remove(Name name);,在词典的所有操作中,最基本的只有 3 种:Find(搜索)、Insert(插入)、Remove(删除)。在选
42、择词典的表示时,必须确保这几个操作的实现。,通常,用文件(表格)来表示实际的对象集合,用文件记录(表格的表项)来表示单个对象。这样,词典中的对将被存于记录(表项)中,通过表项的关键码(对的名字)来标识该表项。表项的存放位置及其关键码之间的对应关系可以用一个二元组表示:(关键码key,表项位置指针addr)这个二元组构成搜索某一指定表项的索引项。考虑到搜索效率,可以用顺序表方式、二叉搜索树或多路搜索树方式组织词典。本节讨论另一种组织词典的方法,即散列表结构。,静态散列方法,散列方法在表项存储位置与其关键码之间建立一个确定的对应函数关系Hash(),使每个关键码与结构中一个唯一存储位置相对应:Ad
43、dress Hash(Rec.key)在搜索时,先对表项的关键码进行函数计算,把函数值当做表项的存储位置,在结构中按此位置取表项比较。若关键码相等,则搜索成功。在存放表项时,依相同函数计算存储位置,并按此位置存放。这种方法就是散列方法。,在散列方法中使用的转换函数叫做散列函数。按此方法构造出来的表或结构就叫做散列表。使用散列方法进行搜索不必进行多次关键码的比较,搜索速度比较快,可以直接到达或逼近具有此关键码的表项的实际存放地址。散列函数是一个压缩映象函数。关键码集合比散列表地址集合大得多。因此有可能经过散列函数的计算,把不同的关键码映射到同一个散列地址上,这就产生了冲突。示例:有一组表项,其关
44、键码分别是 12361,07251,03309,30976,采用的散列函数是 hash(x)=x%73+13420 其中,“%”是除法取余操作。则有:hash(12361)=hash(07250)=hash(03309)=hash(30976)=13444。就是说,对不同的关键码,通过散列函数的计算,得到了同一散列地址。我们称这些产生冲突的散列地址相同的不同关键码为同义词。由于关键码集合比地址集合大得多,冲突很难避免。所以对于散列方法,需要讨论以下两个问题:,构造散列函数时的几点要求:散列函数应是简单的,能在较短的时间内计 算出结果。散列函数的定义域必须包括需要存储的全部 关键码,如果散列表允
45、许有 m 个地址时,其 值域必须在 0 到 m-1 之间。,散列函数,对于给定的一个关键码集合,选择一个计算简单且地址分布比较均匀的散列函数,避免或尽量减少冲突;拟订解决冲突的方案。,直接定址法 此类函数取关键码的某个线性函数值作为散列地址:Hash(key)a*key+b a,b为常数 这类散列函数是一对一的映射,一般不会产生冲突。但是,它要求散列地址空间的大小与关键码集合的大小相同。,散列函数计算出来的地址应能均匀分布在整 个地址空间中:若 key 是从关键码集合中随 机抽取的一个关键码,散列函数应能以同等 概率取0 到 m-1 中的每一个值。,示例:有一组关键码如下:942148,941
46、269,940527,941630,941805,941558,942047,940001。散列函数为 Hash(key)=key-940000 Hash(942148)=2148 Hash(941269)=1269 Hash(940527)=527 Hash(941630)=1630 Hash(941805)=1805 Hash(941558)=1558 Hash(942047)=2047 Hash(940001)=1 可以按计算出的地址存放记录。数字分析法,设有 n 个 d 位数,每一位可能有 r 种不同的符号。这 r 种不同的符号在各位上出现的频率不一定相同。可根据散列表的大小,选取其中
47、各种符号分布均匀的若干位作为散列地址。计算各位数字中符号分布均匀度 k 的公式:其中,表示第 i 个符号在第 k 位上出现的次数,n/r 表示各种符号在 n 个数中均匀出现的期望值。计算出的 k 值越小,表明在该位(第 k 位)各种符号分布得越均匀。,9 4 2 1 4 8位,1=57.60 9 4 1 2 6 9位,2=57.60 9 4 0 5 2 7位,3=17.60 9 4 1 6 3 0位,4=5.60 9 4 1 8 0 5位,5=5.60 9 4 1 5 5 8位,6=5.60 9 4 2 0 4 7 9 4 0 0 0 1,若散列表地址范围有 3 位数字,取各关键码的 位做为记
48、录的散列地址。也可以把第,,和第位相加,舍去进位位,变成一位数,与第,位合起来作为散列地址。,数字分析法仅适用于事先明确知道表中所有关键码每一位数值的分布情况,它完全依赖于关键码集合。如果换一个关键码集合,选择哪几位要重新决定。除留余数法设散列表中允许地址数为 m,取一个不大于 m,但最接近于或等于 m 的质数 p 作为除数,利用以下函数把关键码转换成散列地址:hash(key)=key%p p m其中,“%”是整数除法取余的运算,要求这时的质数 p 不是接近2的幂。,示例:有一个关键码 key=962148,散列表大小 m=25,即 HT25。取质数 p=23。散列函数 hash(key)=
49、key%p。则散列地址为 hash(962148)=962148%23=12。可以按计算出的地址存放记录。需要注意的是,使用上面的散列函数计算出来的地址范围是 0到 22,因此,从23到24这几个散列地址实际上在一开始是不可能用散列函数计算出来的,只可能在处理溢出时达到这些地址。平方取中法此方法在词典处理中使用十分广泛。,它先计算构成关键码的标识符的内码的平方,然后按照散列表的大小取中间的若干位作为散列地址。设标识符可以用一个计算机字长的内码表示。因为内码平方数的中间几位一般是由标识符所有字符决定,所以对不同的标识符计算出的散列地址大多不相同,即使其中有些字符相同。在平方取中法中,一般取散列地
50、址为 2 的某次幂。例如,若散列地址总数取为 m=2r,则对内码的平方数取中间的 r 位。如果 r=3,所取得的散列地址参看图的最右一列。,标识符 内码 内码的平方 散列地址A 01 01 001A1 0134 20420 042A9 0144 23420 342 B 02 4 004 DMAX 04150130 21526443617100 443DMAX1 0415013034 5264473522151420 352 AMAX 01150130 135423617100 236AMAX1 0115013034 3454246522151420 652,标识符的八进制内码表示及其平方值,折