论文阅读——R树

时间:2021-06-13 06:33:24

原文地址:http://blog.sina.com.cn/s/blog_672446ba0100uy2f.html



   这篇文章发布于1990年。相比R树那篇论文,这一篇要晦涩得多。主要的内容是对 R树的改进。
    我原本打算全文翻译的,但是实在是太耗时间了,所以前面翻译的还算比较认真,后面的质量就越来越差了,差不多算是鸟语了。翻译这篇东西花了一周,虽说有我效率太低的原因,可是实在太久了,以后再也不干全文翻译这种事了。现在这个篇幅大概有一万多字,比R树那一篇要完整,主要内容都翻译出来了。

摘要
    R树是最为著名的 矩形访问方法(Access method)之一 ,其算法的基本原理是对中间节点的外接矩形面积进行启发式<* 译者注:其实我不明白什么是启发式,也不明白R树哪里启发式了? *>的优化。我们在一个标准实验平台下对各种数据、各种查询、各种操作进行了大量的实验,并根据实验结果设计了R*树。 R*树可以对路径中每个外接矩形的面积、边长(margin)和叠加程度进行综合优化,并将整个路径上所有外界矩形的优化结果合并起来考虑。 (原文是incorporates a combined optimization of area, margin and overlap of each enclosing rectangle in the directory)。我们在标准的实验平台下对两种数据结构进行了详细的性能比较,结果证明,与现有的各种R树变种相比,R*树都有明显的性能优势。这里的R树变种包括Guttman的线性R树、二次R树(或者叫平方R树),也包括Greene的R树变种。R*树具有全面的性能优势,不论是针对矩形数据还是针对多维点数据、不论是查询操作还是地图叠加等各种空间操作,R*树的性能优势均有所表现。从实际应用的角度来讲,R*树在下面两个方面对用户有强烈的吸引力:第一,R树不论对点还是其他各种空间数据都能提供有效的支持;第二,R*树的实现代价仅比R树略高。

1、引言
    本文将对复杂的空间对象进行近似,并在此基础上研究 空间访问方法(Spatial Access Method,SAMs )。对空间对象的近似是用空间对象的最小外包矩形来表示的,外包矩形的各边与数据空间的各坐标轴平行。这种近似方法比较简单,但它有一个重要的特性,即这种方法可以用少量的字节来代表复杂的空间对象。虽然这种近似方法会导致大量的信息丢失,但空间对象的最小外包矩形仍然保存了对象的一些必要的几何属性,包括空间对象的位置、空间对象在各坐标轴方向的扩展范围。
    <* 译者注:下面这一段没看懂,可能是这样翻译*>从我们列出的参考文献【SK 88】中我们知道,已知的空间访问方法所组织的最小外包矩形是基于一种底层的点访问方法(Point Access Method,PAM),点访问方法使用了下面三种技术中的一种:切割(clipping),转换(transformation),重叠区(overlapping region)。储存矩形的最著名的空间访问方法是R树。按照我们的分类原则,R树的基础是一种点访问方法B+树。B+树使用了重叠区方法<* 译者注:B+树使用了重叠区方法吗?*>,因此R树很容易实现,这种特性有助于R树的普及
    R树算法基于启发式优化,所使用的优化标准(optimization criteria)是:最小化中间节点中的每个外接矩形的面积。这是一种最直接的优化标准,但并没有证据证实这是最佳的优化标准。这种优化标准会带来类似下述的一些疑问:为什么不最小化最小外包矩形的边或者重叠区(而最小化最小外包矩形的面积)?为什么不优化存储空间的利用率?为什么不同时优化上述的所有内容?上述几种标准能不能以一种消极的方式进行交互(interact in a negative way)?只有使用工程学的方法才能找出各种优化原则之间的最佳结合
    采取工程学方法的必要条件是要有一个标准的实验平台,供我们用各种不同的数据、不同的查询、不同的操作来运行大量的实验。我们实现了这样一个标准的实验平台,并使用这个平台来进行性能对照,尤其是对点访问方法进行性能对照。<* 译者注:为什么是对点访问方法进行性能对照呢?另外,原文在这里标了一个参考文献【KSSS 89】*>
    作为我们研究的结果,我们设计了一种新的R树变种,称为R*树,在所有的实验中,R*树表现出的性能都比现有的各R树变种更出色。在很多与现实相近的数据和操作中,R*树所表现出的性能优势是相当可观的。除了常规的点查询、矩形求交、矩形邻接查询(rectangle enclosure query)外,我们还分析了R*树在地图叠加操作时的性能。我们调用了空间链接(Spatial join)操作,因为空间链接是在地理和环境数据库系统中最重要的操作之一。
    本文按如下结构组织:第二章中,我们介绍了R树原则,包括它的优化标准;第三章中,我们介绍了Guttman和Greene提出的R树的几种变种;第四章中,我们详细介绍了我们所涉及的R*树;第五章中,我们说明了R*树与R树各变种进行对比的结果;第六章总结全文。

2、R树的算法原则和可能的优化标准
    R树的结构与B+树类似,它可以完整地存储多维矩形而不需要将其切成若干块,也不需要将其转换为更高维的点。
    在R树的非叶节点中,条目(entry)的存储形式是{cp,Rectangle}。其中,cp是一个指针,指向这个条目对应的子节点在R树中的存储位置;Rectangle是一个最小外包矩形,它是“这个条目对应的子节点中的所有条目的矩形”的最小外包矩形。叶节点中条目的存储形式是{Oid,Rectangle},其中Oid指向数据库中的一条记录,描述了一个空间对象;Rectangle是这个空间对象的外接矩形。另外,叶节点中条目也可能以这种形式存储:{dataobject,Rectangle},<* 作者注:这可能意味着R树直接保存空间对象,而不是与另外的一个数据库向连*>。后一种存储方式的基本结构与前一种类似。下文主要讨论前一种存储方式。
    记M是一个节点中条目数的上限,规定一个参数m作为节点中条目数的下限,要求2 <= m <= M/2。则R树具有如下性质:
# 根节点如果不是叶节点,那它至少有两个子节点
# 一个非叶节点如果不是根节点,那它的子节点数都介于m与M之间
# 一个叶节点如果不是根节点,那他包含的条目数介于m与M之间
# 所有的叶子节点都在同一层
    R树、R*树都是完全动态的,插入、删除操作可以与查询操作混合进行,而且不需要进行定期的全局重建。很显然,这种结构允许不同子树的最小外包矩形发生重叠,因此这种结构在进行精确匹配搜索(exact match query)的时候会产生多条搜索路径。对于这个问题可以参看参考文献【Gut 84】。稍后本文将会论证上述允许重叠区域的技术并不一定导致较差的平均检索性能。另外,本文引入一个术语“路径矩形”(Directory Rectangle),其几何含义是“包围若干下级矩形的最小外包矩形”。(原文是which is geometrically the minimum bounding rectangle of the underlying rectangles).
    R树主要是下面这个问题。对给定的任意一组矩形,将其分为若干个子集,每个子集中的矩形数介于m与M之间,然后对子集动态建立外包盒,要求这样建成的R树应当能高效地支持任意大的查询矩形提出的任意一种检索操作。现在已经知道,能够影响检索性能的参数有很多个,且这些参数间会以一种复杂的方式相互影响。因此,对任意一个参数的优化都有可能对其他参数造成影响,从而使得R树的整体性能发生恶化。此外,由于每个最终记录的矩形(原文是data rectangle)会有不同的大小和形状、路径矩形会动态地变大和缩小,所以很难确定对某个参数进行的某种优化是否真的能够成功。因此,我们使用了基于大量不同实验的启发式解决方法来解决这个问题。上述的大量不同实验是在一个系统化的框架中完成的。
    本章将讨论与检索性能有密切关系的几个参数。此外,本章还会分析这几个参数间的相互依存关系、个参数的优化标准
    原则O1:路径矩形所覆盖的面积应该最小化。也就是说,被最小外包矩形所覆盖、但是没有被其下级矩形(原文是enclosed Rectangle)所覆盖的区域,不妨称之为“死空间(dead space)”,应当最小化。考虑到在进行空间操作的时候,我们需要决定树里的哪些路径要被遍历,而最小化的路径矩形覆盖面积可以令我们在树的较高层次忽略掉不需要遍历的路径,因此这一操作有助于提升性能。
    原则O2:不同路径间矩形的重叠应当最小化。这个操作也可以减少需要被遍历的路径数目。
    原则O3:路径矩形的边长应当最小化。这里所谓的边长是指矩形的所有边或棱的长度之和<* 译者注:含义类似于周长*>。面积确定的情况下边长最小的形状是正方形。因此如果最小化边长而不是最小化面积,将会是路径矩形更接近正方形。如果查询矩形是较大的正方形,那么最小化的边长可以提供较高性能(这句话原文是Essentially queries with large quadratic query rectangles will profit from this optimization)。更重要的是,最小化边长可以在根本上改善树的结构。正方形更容易被包围,因此如果某层的外包盒更接近正方形,那么其上一层的路径矩形就会相对较小。因此如果按照各边的长度的方差来给矩形分组,将边的方差较小的分为一组并构建外包盒,则这种分组方法可以减少路径矩形的面积。(这句话的原文是Thus clustering rectangles into bounding boxes with only little variance of the lengths of the edges will reduce the area of directory rectangle)
    原则O4:存储时的空间利用率应当优化。较高的存储空间利用率可以令树的高度保持较低,所以通常会减低查询成本。有证据表明,查询矩形越大受这个参数的影响就越大,因为如果检索涉及的结果有很多,那么每个节点中的矩形数目就会对检索性能产生较大影响。(这句话原文是这样的:Evidently, query types with large query rectangles are influenced more since the concentration of rectangles in several nodes will have a stronger effect if the number of found keys is high)。
    如果要令路径矩形的面积和互相之间的重叠面积较小,就需要减小对节点中矩形存储数目的限制,因此最小化这两个参数会导致较低的存储效率。另外,如果要做到O1或者O2,就需要以更*的方法选择每个矩形的分组,而这将导致上层矩形的形状变得不像正方形。如果要做到O1,那么路径矩形间的重叠就会增加,因为这会降低数据空间的覆盖程度。<* 译者注:我没有理解这句话的逻辑,那个原则导致什么降低了?原文是the overlap between directory rectangles may be affected in a positive way since the covering of the data space is reduced*>。至于对几何形状的优化,如果要最小化边长,就会降低存储效率。然而,由于接近正方形的路径矩形更容易包围起来(packing better),所以这容易保证较高的存储效率<* 译者注:这是什么逻辑?*>显然,对于一个充分大的查询矩形来说,存储效率对性能的影响另外三个参数O1-O3的影响大得多。

3、R树的变种
    R树是一种动态的结构。因此所有优化检索性能的方法都是在插入新的数据矩形的时候实施的。插入算法会调用另外两个算法,在这两个算法中一些关键的决策能保证较高的检索性能。这两个算法分别是算法ChooseSubTree和算法Split。算法ChooseSubtree从根开始运算,向下一直检索至叶节点,在每一层都选择最适合容纳新条目的子树。如果算法ChooseSubtree最终找到的节点已经保存了M个条目,不能再插入更多条目了,则调用算法Split。算法Split会用最适当的方法把M+1个条目分配到两个节点当中去。
    下面我们将分析和讨论现有的各种R树变种提出算法ChooseSubtree和算法Split。首先考虑Guttman提出的最普通的R树。

------------------------------------------
算法ChooseSubtree
步骤CS1:记N为根节点
步骤CS2:如果N是叶节点则返回N,否则选择N中最适合插入新数据的条目,选择标准是当插入新数据时,相应矩形的面积增加最小。如果某两个条目插入新数据后面积增加程度相等,则选择其中面积较小的那个。
步骤CS3:将N置为步骤CS2中选出的条目所指向的那个节点,返回步骤CS2继续运算
------------------------------------------

    很明显,这种优化方法是试图最小化路径矩形的覆盖面积,也就是前述的原则O1。这个操作可能还会降低矩形间的重叠,对CPU的消耗也相对较低。
    Guttman讨论了三种Split算法,其时间复杂度分别与节点中的条目数成指数、平方、线性关系。三种算法的设计原则都是减小分裂后产生的两个矩形的覆盖面积。指数分裂算法可以得出面积最小的分裂结果,但是会消耗过多的CPU时间。其他的两个算法都是试图得出近似最优的结果。在Guttman的实验中,线性分裂算法的结果与平方分裂算法的结果表现出了相近的检索性能。我们也实现了基于线性分裂算法的R树和基于平方分裂算法的R树,但是我们用不同分布、不同叠加状况、不同的M和m、不同数目的数据记录进行了大量测试后认为,基于平方分裂的R树产生了更好的性能。本文第5章详细列出了测试结果。基于我们的实验结果,本文仅详细讨论平方分裂算法。

-----------------------------------------
算法QuadraticSplit:把M+1个条目分成两组
步骤QS1:调用算法PickSeeds选择两个条目,分别作为两组的第一个条目
步骤QS2:循环调用算法DistributeEntry,直至所有的条目都已经被分配到某一组、或者某一组已经有了M-m+1个条目
步骤QS3:如果还有余下的条目,则将其全部放入条目较少的那个组里,以保证这个组里的条目数大于下限m
------------------------------------------
算法PickSeeds
步骤PS1:对于每一对条目E1和E2,生成其最小包围矩形R,计算d = area(R) – area(E1.Rectangle) – area(E2.reactangle)。
步骤PS2:选择得出的D最大的那一组E1和E2
------------------------------------------
算法DistributeEntry:
步骤DE1:调用算法PickNext选择一个要被分配的条目
步骤DE2:将选出的条目插入一个组中,选择组的原则是加入这个条目后外包矩形的面积增加较小。如果插入新条目后两个组外包矩形的面积相当,则选择插入外包矩形面积较小的那个组里。若两个组的面积也相当,则插入条目较少的那个组里
------------------------------------------
算法PickNext:
步骤PN1:对于尚未分组的每个条目E,计算d1 = 当把E插入第一组时外包矩形面积增加的程度,d2 = 插入第二组时外包矩形面积增加的程度
步骤PN2:返回d1和d2面积之差的绝对值最大的那个条目
------------------------------------------

    算法PickSeeds把如果放在一起会产生最多面积浪费的两个矩形选出来。在这种情况下(原文是In this sense,没有说明是什么情况)两个距离最远的矩形会被选出来。值得一提的是,如果需要被分裂的矩形大小差距很大或者它们之间有大量重叠,那选出来的种子(seeds)通常是面积较小的矩形。算法DistributeEntry把剩余的条目根据“面积最小”的标准进行分配。算法PickNext选择在相应情况下对面积影响最大的条目(entry with the best area-goodness-value)。
    这套算法中,如果算法PickSeeds选出的是较小的种子,就有可能产生问题。如果有一个d维矩形,它在d-1维里与种子相距很远,但是在剩下的那一维里与种子的坐标很接近,那么这个矩形将优先被分配到相应的种子那一组里。这种插入导致的“面条形”外包矩形的面积增加会很小,但这个矩形的长度很长。这会导致糟糕的分裂结果。<* 译者注:这显然是因为算法只考虑了面积,没有考虑其他因素*>。另外,算法倾向于把第a+1个待分配的矩形与第a个待分配的矩形放入同一个种子中(原文是the algorithm tends to prefer the bounding rectangle, created from the first assignment of a rectangle to one seed)。这个因为当第a个矩形放入某个种子后,种子的外包矩形就会变大,那么再放入下一个条目的时候外包矩形的面积增加幅度可能相对较小。这种恶性循环有可能一直进行。另一个问题是,如果一个组中保存的条目数到达上限M-m+1,那么剩下的条目不管其形状如何都会被分配到另外一个组里。第4.3节的图1<* 图略,下同*>显示的例子显示了上述的所有问题。这些问题可能使分裂的结果又较大的重叠,也可能使条目的分配不均匀,而后者会降低存储效率。
    我们用基于平方分裂算法的R树测试了不同的m值,m值分别为M值的20%、30%、35%、40%、45%。结果表明当m是M的40%时检索性能最好。
    在比较R树与其他保存矩形的数据结构的时候,Greene提出了下面这个替代的分裂算法。为了选择插入新条目的最佳路径,他使用了Guttman的算法ChooseSubtree。

------------------------------------------
算法GreeneSplit:把M+1个条目分为两组
步骤DS1:调用算法ChooseAxis,以决定接下来的分裂操作要与哪个轴垂直
步骤DS2:调用算法Distribute
------------------------------------------
算法ChooseAxis:
步骤CA1:调用PickSeeds(见p 5<* 译者注:这里没有说明p 5是什么*>),选择当前节点中两个距离最远的矩形
<* 下面三个步骤我不太懂,可能是这么翻译*>
步骤CA2:对每个轴记录两个种子的距离(但原文用的是separation而不是distance)
步骤CA3:对CA2算出的距离进行标准化(Normalize),标准化的方法是先求出所有节点的总外包矩形,然后用CA2的每个距离、除以这个总外包矩形在相应轴上的投影。(原文是Normalize the separations by dividing them by the length of the nodes enclosing rectangle along the appropriate axis)
步骤CA4:CA3算出的距离称为标准化距离(Normalized Separation),选出标准化距离最大的轴作为返回值,算法结束。
------------------------------------------
算法Distribute:
步骤D1:沿选定的轴,根据矩形在轴上投影的左坐标给矩形排序
步骤D2:将前(M+1)/2个条目放入一个组,另外(M+1)/2个条目放入另外一组
步骤D3:如果M+1是奇数,那么经过步骤D2后还会剩下一个条目,将这个条目加入一个组当中,选择目标组的原则是将这个条目插入这个组后,相应组的外包矩形面积增加较少。
------------------------------------------

    Greene的分裂算法基本上只考虑了一个标准,即选择一个合适的坐标轴作为分裂的基础。虽然选择一个正确的坐标轴确实十分重要,但是我们的实验表明,必须使用更多的几何优化标准,以更好地提升检索性能。除了分组的问题以外,在某些情况下Greene的分裂算法无法选择出正确的轴,因此可能导致很糟糕的分裂结果。图2b描绘的情形就是这样的。

4、R*树
4.1、选择子树
对于选择理想插入位置的问题,之前的R树版本只考虑了面积参数。在我们的试验中,我们测试了面积、边长、重叠区面积三个参数的不同组合。其中重叠区面积是这样定义的:
设E1——Ep是节点中的p个条目
Overlap(Ek) = 第K个条目对应矩形与节点中的其他p-1个条目对应矩形的重叠面积之和<* 公式略。译者注:注意是重叠面积之和,如果是三个矩形重叠,那么重叠面积算3遍*>
根据我们的实验,检索性能最高的R树变种应当使用下面的算法

------------------------------------------
算法ChooseSubtree:
步骤CS1:记根节点为N
步骤CS2:如果N是叶节点,返回N,算法结束。如果N不是叶节点,再考察N中的指针是指向叶节点还是指向中间节点。——如果指向叶节点,则应最小化重叠面积。具体做法是,选择最适合容纳新记录的条目,选择的标准是:加入新记录后相应条目的重叠面积增加程度最小。如果重叠面积增加相等,则下一个判断原则是面积增加程度最小。如果面积增加程度也相等,则选择矩形面积最小的那个条目。——如果不是指向叶节点,也就是说指向中间节点,则应最小化面积。具体做法是,选择最适合容纳新记录的条目,选择标准是插入新的记录后相应条目的面积增加最小。如果面积增加程度相等,则选择面积最小的那个条目。<* 译者注:原文的描述是Choose entry whose rectangle need least overlap/area enlargement,注意,它说的是Entry的Enlargement而不是N的Enlargement,这其实与前文overlap的定义不一致。但是其含义是可以理解的。*>
步骤CS3:把N置为步骤CS2中选出的那个条目的指针指向的节点,然后返回步骤CS2重新计算。
------------------------------------------

    对于寻找最佳非叶节点的方法,Guttman提出的最原始的方法比其他备选算法的性能都高。但是对于选择最佳的叶节点,最小化重叠面积可以令性能稍有提高。
    在我们提出的这个算法里,CPU的时间消耗与节点中的条目数的平方成正比,这是因为对每个条目都要计算它与其他条目的重叠面积。然而,对于较大的节点我们可以减少对一些条目的计算,因为如果一个矩形距离待插入的记录太远,那它基本不可能是插入目标。因此,为了降低CPU时间消耗,上述的算法可以进行如下修改:

------------------------------------------
算法修改:寻找接近最优的插入目标(原文是determine the nearly minimum overlap cost,寻找接近最小的重叠面积)
步骤1:根据插入新记录后矩形面积增加的程度<* 注意是面积而不是重叠面积*>把N里面的各个条目进行升序排列
步骤2:将序列中的前p个条目记为条目组A
步骤3:将A中的条目视为N中的所有条目,调用前述的步骤CS2选择最优的插入位置。
------------------------------------------

    对于二维的空间,我们发现如果p是32的时候检索性能不会受到影响<* 译者注:它这里想表达的意思可能是当p为32时这种修改后的算法只会使插入性能提高、不会使检索性能降低*>。对于更高维的情况现在尚不清楚。虽然即使经过这种修改我们的算法仍会比原始算法消耗更多的CPU时间,但是却可以降低磁盘访问次数,因为这避免了在插入前进行额外的匹配查询(match query)<* 译者注:我很不解这个结论,因为从描述上看,如果一次读取一个完整页面也就是一个节点的话,两种算法的磁盘访问次数都等于树的高度,不存在降低磁盘访问次数的问题。除非每次只读若干条目,不读一个完整的节点*>。
    实验表明,对算法ChooseSubtree的优化可以提升检索性能,尤其是在下面这种情况下的性能提升尤其明显:查询矩形较小,而被查询的数据文件由一些分布不均匀的小矩形或者点构成。其他情况下Guttman算法的性能与我们这个算法的性能相似,因此我们这个算法的主要优势在于健壮性的提升。
4.2、R树的分裂
    R*树使用下述方法寻找最佳的分裂方案。沿着每个轴,按照先左坐标后右坐标的方法对条目进行排序,形成若干序列。对于需要分裂的M+1个条目,每个序列都有M-2m+2种可能的分配方法。其中第k中分配方法是把前m-1+k个条目分为一组,剩下的分为另一组。
    对每一种分配方法,计算其效益值(goodness value),<* 译者注:其实是反效益值,越小越好*>根据效益值决定最终选用哪一种分配方法。有三种不同的效益值计算方法,又有三种不同的效益值使用方法,我们试验了不同组合的性能。

------------------------------------------
三种计算方法:
Area-value = area[bb(first group)] + area[bb(second group)]
Margin-value = margin[bb(first group)] + margin[bb(second group)]
Overlap-value = area[ bb(first group) ∩ bb(second group)]
公式中的bb表示一组矩形的外包盒(bounding box)
------------------------------------------
三种使用方法:
(1)某个轴或者某个序列里的最小值<* 译者注:每个轴有M-2m+2个分组方法,每种分组方法都有3个效益值,这里想要表达的可能是3M-6m+6个效益值中的最小值,也可能是这里所述的最小值有三个。译者倾向于第一种解释。这样的结果是用户指定一个轴,返回一个针对此轴效益值最低的分组方法*>
(2)各轴每种分组方法的3个效益值求和,选其中的最小值<* 译者注:这应该也是用户指定一个轴,返回一个针对此轴效益值和最低的分组方法*>
(3)全局最小值<* 返回全局效益值最小的分组方法,不要求用户指定一个轴*>
<* 译者再注:从下文的算法上看,好像这三种使用方法不是这样解释的。不过这个跟最终的算法关系不太大*>

------------------------------------------

    得到的值用于决定依哪个轴进行分裂,或者直接决定最终分组方案(依照指定的轴)。总体来说,性能最高的R*树是根据下面这种算法进行构建的。

------------------------------------------
算法Split
步骤S1:调用算法ChooseSplitAxis来决定依哪个轴进行分裂(即分裂线垂直于哪个轴)
步骤S2:调用算法ChooseSplitIndex,决定沿这个轴的最佳分组方法
步骤S3:按照上一步的结果把条目分成了两组
------------------------------------------
算法ChooseSplitAxis
步骤CSA1:对每一个轴都进行如下操作:对所有条目先根据左坐标再根据右坐标进行排序,然后按照上文所述方法对每个轴都确定一组分组方案,然后计算这个轴上所有分组方案的margin-value之和,记为S。
算法CSA2:将S值最小的轴作为分裂轴
------------------------------------------
算法ChooseSplitIndex
步骤CSI1:沿着选定的分裂轴,选择overlap-value最小的分组方案。如果有两个方案的overlap-value相等,则选择area-value最小的方案。
------------------------------------------

    对于分裂算法,我们在试验取的m分别为M的20%、30%、40%和45%。实验表明当m是M的40%时性能最好。另外,我们在同一个R*树的整个生命周期里使用不同的m值,试图使几何属性与存储效率产生联系。但是即使采用下面这个方法,检索性能仍然比不采用时差:分别用用m1 = 0.3M,m2 = 0.4M进行分裂操作,如果split(m2)产生了重叠而split(m1)没有产生重叠<* 译者注:原文是yield overlap,这里所说的重叠是什么重叠?难道一定能做出不重叠的分裂方案?不能这么保证吧*>,则采用Split(m1)。否则采用split(m2)。
    关于R*树分裂算法的性能,我们提出如下几点。对于每一个轴或者说维度,需要进行两次排序,时间复杂度是O(M log(M))。从一个实验结果上来看,这占用了分裂算法一半的时间。剩下的时间是这样消耗的:对于每一个轴,计算边长需要2(2(M-2m+2))次计算,计算重叠面积需要2(M-2m+2)次计算。
4.3、强制插入
    不管是R树还是R*树,在把条目分配给节点的过程中都是不确定的。也就是说,用不同的次序插入同一组条目会建成不同的树。由于这个原因,R树建立早期插入的节点会干扰R树的结构。R树生长早期插入的数据会按照当时的情况被放在某个路径矩形里,但是以当前的观点来看它所在的位置已经不能保证较高的检索性能了。进行分裂操作的时候,路径矩形会进行局部的重组,但是这种重组太简单,因此急需一种更加强大的、更全局性的机制来对数据结构进行重组。
    记录的删除有可能导致节点下溢,如果下溢的节点在原来的父节点位置下进行合并,那上面说的这个问题会更严重。因此现有的对待下溢节点的方法是删除节点,把里面的记录重新插入R树。这种方法可以通过调用算法ChooseSubtree来把下溢节点中的记录分配到不同的节点中。
    正如预计的那样,这种对旧数据矩形的删除和再插入会提升检索性能。我们使用线性R树进行了下面这个简单的实验。插入20000个均匀分布的矩形,删除前面10000个,然后把它们重新插入。实验结果是,对不同类型的查询而言,处理后的R树查询性能提升了20%到50%。因此随机删除一半的数据然后把它们重新插入回树里似乎是一种非常简单的调整R树数据文件(datafile)的方法。但是这是一个静态的情景<* 译者注:我不知道它所说的this is指代的是什么。R树应当不是一个静态的情景啊?*>,对于接近静态的数据文件来说打包算法(the pack algorithm)是一种很复杂的解决方法。
    为了实现动态的重组,R*树强制将插入路径上的所有条目进行重新插入。下面这些算法都认为R*树允许将条目插入树的各层当中,这个假定类似于算法Deletion的要求(原文是the following algorithm is based on the ability of the insert routine to insert entries on every level of the treeas already required by the deletion algorithm)。除了对上溢情况的处理外,这个算法与Guttman所述的原始算法相同,因此仅在这里简要介绍。

------------------------------------------
算法InsertData
步骤ID1:调用算法Insert以便插入一个新的数据矩形。由于插入的不是一个最终数据而是一个中间节点中的条目,所以还需要传入一个参数,即这个中间节点所在的层。下文中这个参数被翻译为“层号”。
------------------------------------------
算法Insert
步骤I1:调用算法ChooseSubtree,将层号作为参数传入,以寻找适合插入条目E的节点N
步骤I2:如果N的条目数小于M,就将E插入N。如果N有M个条目,则调用算法OverflowTreatment,并把层数作为参数传入这个算法中以便重新插入或者分裂
步骤I3:如果调用了算法OverflowTreatment并进行了分裂,则需要判断其上层是否也需要调用算法OverflowTreatment,如果有必要则调用之。如果算法OverflowTreatment令根节点也发生了分裂,则建一个新的根节点
步骤I4:调整插入路径上的所有外包矩形,使外包矩形在范围上最小化。
------------------------------------------
算法OverflowTreatment:
<* 译者注:原文没有说明这个算法的参数是一个节点还是一个子树,从算法描述上来看,这应该是对一个节点的操作*>
步骤OT1:如果传入的层号不是根、并且这是本次数据插入过程中<* 译者注:注意是本次数据插入过程中而不是数据重插入过程中*>在相应层次上第一次调用算法OverflowTreatment,则调用算法ReInsert,否则调用算法Split
------------------------------------------
算法ReInsert
步骤RI1:对于节点N中的M+1个条目,计算其矩形中点与N的矩形中点之间的距离
步骤RI2:按照步骤RI1算出的距离吧条目按降序排列
步骤RI3:从N中移除前P个条目,然后调整N的外包矩形
步骤RI4:在步骤RI2所定义的序列中,从高到低或者从低到高,调用算法Insert来重新插入这些条目。从高到低的重新插入称为“远端重插入(far insert)”,反之称为“近端重插入(close insert)”
<* 译者注:我不明白它在步骤RI3里移除了距离中心最远的p个条目,然后把距离中心近的其他条目进行重新插入?那移除的那些怎么处理?文中没讲,怀疑是这样的:把步骤RI2中的前p项进行重新插入,插入的次序是步骤RI1里定义的次序。另外,把与中心的距离视作一个参数,这或许是一个不错的方法*>
------------------------------------------

    插入一个新的数据矩形后,每层的第一次上溢处理都对p个条目进行了重新插入。如果重新插入的位置还是原来的位置,那么将导致这个节点发生分裂。相应的,如果重新插入到其他的位置,则可能引起其他节点的分裂,但是很多情况下也有可能不发生分裂。考虑到性能的问题,参数p对叶节点和非叶节点可能取不同的值。我们也对不同的p值进行了试验,结果表明,对叶节点和非叶节点的p都取M的30%能够获取最佳性能。另外,对于所有的数据和查询而言,近端重插入比远端重插入性能要好。因为近端重插入倾向于把条目插入条目原本的位置,这正是我们所希望的,因为这样一来外包矩形的面积会减小,那么在下一次调用算法ChooseSubtree的时候这个节点被选中的可能性就较小。
    <* 译者注:这段话描述不是很清楚,我感觉可能是这样。第一,整个插入路径上的所有相关条目都要被插入一遍。第二,插入的时候,如果上溢,先不进行分裂,而是把上溢的那个节点里的p个条目再重新插入一遍。于是这个逻辑就比较复杂了。首先,插入一个数据记录的时候是插入到叶节点,会发生分裂,分裂向上传递,整个树被处理一遍。然后插入倒数第二层的节点,插入的时候有可能上溢从而有更多的倒数第二层的条目需要重新插入,此时再重新插入导致的分裂就直接分裂了,分裂就要向上一层重新插入,此时的插入又要调用OverflowTreatment。看起来是这样的,对于上溢和分裂的处理,R树是算法AdjustTree,R*树使用算法OverflowTreatment来代替算法AdjustTree。R树的做法是需要分裂时分裂、插入上一级。R*树则多了一步重新插入,也多了对整个路径条目的重新插入*>。
    总结一下,我们可以说
第一,强制重新插入改变了相邻节点间的条目,因此可以降低重叠
第二,作为额外的影响,存储效率提高了。
第三,由于进行了较多的重组,因此会发生较少的分裂
第四,由于节点中位于矩形外侧的条目执行了重新插入,因此路径矩形会更接近于正方形。如前文的论述,这个现象是我们所很希望看到的。
    很明显,这个算法会导致更高的CPU时间消耗,因为整个插入路径会被多次访问。但这个问题不会太严重,因为这个操作会减少分裂的次数。实验表明使用了重新插入机制后,插入操作的磁盘访问次数提高了4%,但仍是R树变种里最少的。这很大程度上归功于重新插入机制导致的R*树结构改善。

5、性能对照
    <* 这一章是性能测试和测试结果的对照,很复杂,就不全文翻译了,只把一些关键的内容翻译出来*>
5.1、测试环境设定
    测试的时候会把树里从根到叶的一个枝存在内存里。另外,如果插入或删除操作的时候出现了新的条目,这个条目也会存在内存里。
    测试时页面大小为1024字节,这可以存56个条目,但测试的时候定义M为50。
    测试使用了六个数据文件,包含大概10万个2维矩形。矩形坐标在0到1之间。数据文件的性质用下面几个参数来表示。第一,矩形中心的分布情况。第二,n,矩形数。第三,μ,矩形的平均面积。第四,nv = σ/μ,其中σ表示面积的方差,nv表示面积的标准差。nv与分布无关,矩形的面积与平均面积的差距越大则nv越大,原文还说了依据,nv可以表示平均的重叠面积。
    下面罗列了F1至F6一共六套数据。
    测试使用了三种方式。第一,矩形相交查询,给定一个矩形,要求返回与其相交的所有矩形。第二,点查询,给定一个点,要求返回所有包含这个点的矩形。第三,包含查询,给定一个矩形,要求返回所有包含这个矩形的矩形。
    测试了每次查询需要的磁盘访问次数,为了测试建立树算法的性能还测试了每次插入算法的磁盘访问次数、树建立完成后的存储效率。另外,用空间链接(Spatial Join)操作代表叠加操作进行了测试。对于空间链接的定义是,如果文件A中的一个记录与文件B中的记录相交,则返回一个空间链接结果。对于空间链接结果,测试其每次操作的磁盘访问次数。
5.2、对结果的分析
    R*树性能显然更好
第一,R*树最为健壮。这里的健壮指的是它比其他各种R树变种的各方面性能都要好。
第二,R*树在使用较小的查询矩形的时候性能更好,这是因为随着查询矩形面积变大,存储效率的影响会变大。
第三,在进行查询的时候,R*树的性能是线性R树的400%,是Greene的R树变种的200%,是平方R树的180%。
第四,跟预期的相同,R*树的存储效率最好。
第五,令人惊奇的是,虽然使用了强制插入的方法,但是平均插入时间不仅没有提高,还有所降低
第六,空间链接操作获得的性能提升比其他查询获得的性能提升要高。对空间链接操作,R*树比平方R树高出147%,比Greene的R树高出171%,比线性R树高出261%。
5.3、一种高效的点访问方法
    这一节的主要内容是测试R*树对于访问点数据的性能。之前的访问都是矩形数据。
    仍然测试存储效率、插入时间消耗、查询时间消耗。
    结果表明R*树对于访问点的性能提升比访问矩形的性能提升更好。R*树甚至比双层四叉树(是不是这个意思?原文2-level grid)性能更好。双层四叉树仅在插入操作方面的性能更好,因此看起来它更适合需要经常插入的情景。

6、总结
    实验表明,本文所提出的R*树不论是访问数据库中的多维点还是多维空间数据都有很好的效果。在所有的实验里,R*树的性能都比其他R树变种高。对于点数据的访问,性能提升更明显。R*树甚至于比双层四叉树更好。
    R*树主要是考虑降低面积、边长、路径矩形的重叠。因为综合考虑了三者,R*树即使面对分布非常不规则的数据时也表现得十分稳定。又由于强制插入算法的使用,避免了不必要的分裂,R*树得到了动态的重构,存储效率得到了提升。R*的实现代价仅比R树的实现代价稍高
    在接下来的工作中,我们将研究,如果像参考文献【SK90】所述的那样使用前缀<* 原文是Prefixes,是什么意思?*>或者使用格网逼近(grid approximation),能不能提高删除。我们也会继续改善R*树,使其能更有效的管理多边形。