本文是在https://www.cnblogs.com/cmi-sh-love/p/kong-jian-shud-ju-suo-yinRTree-wan-quan-jie-xi-jiJa.html?share_token=e5b096d7-6dbf-4839-9992-b29913335ba9基础上进行修改和补充的。
第一部分 空间数据的背景介绍
空间数据的建模
基于实体的模型(基于对象)Entity-based models (or object based)
- 0-dimensional objects : 一般使用点point来表示那些对于不需要使用到形状信息的实体。
- 1-dimensional objects or linear objects: 用于表示一些路网的边,一般用于表示道路road。 (polyline)
- 2-dimensional objects or surfacic objects: 用于表示有区域面积的实体。 (polygon)
- 3-dimensional objects or voxel objects:用于表示有空间体积的实体。(polytope)
常用的空间数据查询方式
- 窗口查询:给定一个查询窗口(通常是一个多维矩形,二维为矩形,3维为长方体),返回查询窗口所覆盖包围的物体。

- 点查询:给定一个点,返回包含这个点的所有几何图形。
空间数据的表示方法
key:最小限定矩形、限定框
通常,我们不选择去索引几何物体本身,而是采用最小限定箱MBB(minimum bounding box ) 作为不规则几何图形的key来构建空间索引(类似B树中的key是数轴上的一个点,以该点的值作为key,二维的R树就是以矩形作为key,三维的R树就是以立方体作为Key,R树检索的是key,所以这点务必要搞清楚,不要把指针当成了key)。
-
在二维的情况下,我们称之为最小限定矩形。MBR(minimum bounding retangle)
 -
三维的情况下,我们称最小限定箱MBB(minimum bounding box)

如何搜索key(矩形或者立方体等)
通过索引操作对象的MBB(后面将MBR和MBB不做区分,除非指明维数)来进行查询一共分为两步:
-
Filtering: 过滤掉MBB不相交的数据集,剩下的MBB组成一个超集(下图中红色为待查找MBB,橙色是与之有交集的MBB)。实际上,这个超集从某一层的某个节点开始,其中的多个entry可能满足条件,而这些entry指向的下一层的node中也可能有多个满足条件,所以后续的查找可能需要对超集中的所有子树及叶子节点进行遍历。
 -
Refinement: 计算实际的几何形状会不会满足查询条件,精确化。
·
如何用数据表示一个MBR(MBB)
通常,我们只需要两个点就可限定一个矩形,也就是矩形某个对角线的两个点就可以决定一个唯一的矩形。通常我们使用(左下,右上两个点表示)或者使用右上左下,都是可以的(原则上是越简单也好,或者元组的越小越好)。三维可以用底面的左下角、底面的右上角、顶面的左上角三元组表示。

- 表示一个点的数据:
public class Point{ //用一个类来表示一个点
public Float x;
public Float y
}
- 表示一个MBR的数据
public class MBR{
public Point BottomLeft;
public Point TopRight;
}
判断如何判断key是否有交集
因为B树表示的是一维数轴,所以key的的判断用的是等号(=),因为是个点,但是在二维或者高维空间是一片区域或者空间,却无法用等号,而是要考虑相交。如何判断两个MBR是否相交?
如果一个MBR的任何一个顶点的坐标(MBR2的左下角(x3,y3))位于另一个MBR(MBR1)的xRange和yRangle里面,则说明这两个MBR相交。对于3维空间来说,则要判断8个顶点。

R树
对于B/B+树,由于它的线性特点,通常用来索引一维数据。(比它大的往一边走,比它小的往一边走,但只是在一个维度下进行比较)。B树是一棵平衡树,它是把一维直线分为若干段线段,当我们查找满足某个要求的点的时候,只要去查找它所属的线段即可。这种思想其实就是先找一个大的空间,再逐步缩小所要查找的空间,最终在一个自己设定的最小不可分空间内找出满足要求的解。一个典型的B树查找如下:


要查找某一满足条件的点,先去找到满足条件的线段,然后遍历所在线段上的点,即可找到答案。B树是一种相对来说比较复杂的数据结构,尤其是在它的删除与插入操作过程中,因为它涉及到了叶子结点的分解与合并。
R树的简介
B树是解决低纬度数据(通常一维,也就是一个数据维度上进行比较),R树很好的解决了这种高维空间搜索问题。它把B树的思想很好的扩展到了多维空间,采用了B树分割空间的思想(如果B树在一维的线段进行分割,R树就是在二维甚至多维度的空间),并在添加、删除操作时采用合并、分解结点的方法,保证树的平衡性。因此,R树就是一棵用来存储高维数据的平衡树。

我们说过,B树是采用切分线段来缩小数据查询范围的一种思想,我们又说了,R树是b树的多维版,以及R树也采用了B树的这一种分割的思想,那么,如果说线段的分割是一维的分割(一维空间的分割容易表示,线段[a,b]之外的“区域”表示很简单[0,a],[a,1000]。在B树中,一维的表示具有排他性,每一个点都只能处于某一个叶子节点中,且只能被唯一的内部节点中的唯一的entry表示,其补集是唯一的,且表示比较简单,在对B树进行调整时也不会太复杂。但是,在R 树中,它一维却允许存在交叉,这样,效率肯定没有B树好。具体见下面的案例和描述)。
那二维的分割就应该是区域的分割(但某个区域A之外的“区域”表示起来就非常麻烦,因为你无法用于与区域A的表达形式相同的方式进行表达,比如,区域A=((20,20),(100,100))是左下角为(20,20),右上角为(100,100)的矩形区域,但是在整个空间(比如设定为((0,0),(1000,1000))中,其补集虽然是唯一的,但是表示却不是唯一的(如下图所示),或者说,至少得用4个区域来表示其补集,这个虽然能表示,但是对于不停的插入新的区域到R树中,为了做到所有的区域之间没有重叠,可能需要不断的调整整棵树,带来非常大的调整开销(不是不能做,而是相对于一维的情形的成本太高),所以一般的R树中各个矩形框是允许交叉重叠的。
而三维的就是几何空间的分割了,其补集表示起来更加复杂,需要6个长方体来表示其补集,每插入一个节点,带来的调整开销会更大。
要注意的是R树并不只是二维空间数据的索引而已,它还可以索引三维甚至更高维。

一个三维的R树

此外R树还可以退化成一维,但是分割的线段存在重叠问题,效果不如B树。

R树的数据结构
如上所述,R树是B树在高维空间的扩展,是一棵平衡树。每个R树的叶子结点包含了多个指向不同数据的指针,这些数据可以是存放在硬盘中的,也可以是存在内存中。
根据R树的这种数据结构,当我们需要进行一个高维空间查询时,我们只需要遍历少数几个叶子结点所包含的指针(即缩小到某个区域下去进行查询,还是采用缩小范围的思想),查看这些指针指向的数据是否满足要求即可。这种方式使我们不必遍历所有数据即可获得答案,效率显著提高。下图是R树的一个简单实例:


解释一下这张图。
- 首先我们假设所有数据都是二维空间下的几何形状,图中仅仅标志了R8,R9,R10区域中的数据(形状所代表的物体),其他的叶子节点仅仅用MBB表示。为了实现R树结构,我们用一个最小边界矩形恰好框住这个不规则区域,这样,我们就构造出了一个区域:R8。R8的特点很明显,就是正正好好框住所有在此区域中的数据。其他实线包围住的区域,如R9,R10,R11等都是同样的道理。这样一来,我们一共得到了12个最最基本的最小矩形。这些矩形都将被存储在子结点中。
- 下一步操作就是进行高一层次的处理。我们发现R8,R9,R10三个矩形距离最为靠近,因此就可以用一个更大的矩形R3恰好框住这3个矩形。
- 同样道理,R15,R16被R6恰好框住,R11,R12被R4恰好框住,等等。所有最基本的最小边界矩形被框入更大的矩形中之后,再次迭代,用更大的框去框住这些矩形。
用地图的例子来解释,就是所有的数据都是餐厅所对应的地点,先把相邻的餐厅划分到同一块区域,划分好所有餐厅之后,再把邻近的区域划分到更大的区域,划分完毕后再次进行更高层次的划分,直到划分到只剩下两个最大的区域为止。要查找的时候就方便了。
下面就可以把这些大大小小的矩形存入我们的R树中去了。根结点存放的是两个最大的矩形,这两个最大的矩形框住了所有的剩余的矩形,当然也就框住了所有的数据。下一层的结点存放了次大的矩形,这些矩形缩小了范围。每个叶子结点(R8--R19)都是存放的最小的矩形,这些矩形中可能包含有n个数据。
上图表达的是一个很理想的案例,如果待搜索区域与R8、R12、R16都有交叉,那么搜索路径就不会像上图那样是一条单路径了。
因此,在R树中搜索每个区域或者点并不像B树那样一次就能确定,而是可能需要多次遍历。原因就是因为R树中的MBB是允许交叉的,当查询某个点或者某个区域时,因为该点或者区域可以与多个entry表示的区域有重叠,只有有重叠,就需要遍历该entry所指向的内部节点或者叶子节点。也就是说,对于搜索点或者区域,在R树中,它的搜索路径是一棵树,它包含多条路径。但在B树中,对于每一个点的搜索,是单一的一条路径。
以餐厅为例,假设我要查询广州市天河区天河城附近一公里的所有餐厅地址怎么办?
- 打开地图(也就是整个R树),先选择国内还是国外(也就是根结点)。
- 然后选择华南地区(对应第一层结点),选择广州市(对应第二层结点),
- 再选择天河区(对应第三层结点),
- 最后选择天河城所在的那个区域(对应叶子结点,存放有最小矩形),遍历所有在此区域内的结点,看是否满足我们的要求即可。
R树的查找规则跟查地图很像吧?对应下图:

一个更具体的使用场景
假设我们有一个地图路网要进行道路的快速索引,那么我们可以将每一条路的最小MBB作为R树的数据单元来进行构建R树。

每一条路使用一个最小MBB来进行包裹,使它成为R树的叶子结点(也就是那些数据结点)(路是各种弯道,所以为每条路构建一个MBB,最后就是一大堆密密麻麻的有大量交叉的矩形)

(这里采用的是R树的改进版本R*树)然后对于建立起来的R树(图中浅灰色的矩形不太清晰,但每一个都表示一条路)再进行查找道路的使用就可以使用我们那种“缩小范围”的查找思想。从上往下一层一层查找。

一棵R树满足如下的性质:
- 1. 除非它是根结点之外,所有叶子结点包含有m至M个记录索引(条目)。作为根结点的叶子结点所具有的记录个数可以少于m。通常,m=M/2。
- 2. 对于所有在叶子节点中存储的记录(条目,entry),I是最小的可以在空间中完全覆盖这些记录所代表的点的矩形(注意:此处所说的“矩形”是可以扩展到高维空间的)。
- 3. 每一个非叶子结点拥有m至M个孩子结点,除非它是根结点。
- 4. 对于在非叶子结点上的每一个条目,i是最小的可以在空间上完全覆盖这些条目所代表的点的矩形(同性质2)。
- 5. 所有叶子结点都位于同一层,因此R树为平衡树。
R树的操作
搜索
R树的搜索操作很简单,跟B树上的搜索十分相似。它返回的结果是所有符合查找信息的记录条目。而输入是什么?输入不仅仅是一个范围了,它更可以看成是一个空间中的矩形。也就是说,我们输入的是一个搜索矩形。
先给出伪代码:
Function:Search
描述:假设T为一棵R树的根结点,查找所有搜索矩形S覆盖的记录条目。
- S1:[查找子树] 如果T是非叶子结点:(1)如果T所对应的矩形与S有重合,那么检查所有T中存储的条目,对于所有这些条目,使用Search操作作用在每一个条目所指向的子树的根结点上(即T结点的孩子结点);(2)如果T所对应的矩形和S没有重合,则返回。
- S2:[查找叶子结点] 如果T是叶子结点:(1)如果T所对应的矩形与S有重合,那么直接检查T所指向的所有记录条目,然后返回符合条件的记录;(2)如果T所对应的矩形和S没有重合,则返回。
我们通过下图来理解这个Search操作。

红色查询区域S和绿色的P3子树和P4子树相重叠,所以根据“缩小空间”的思想,只需要遍历P3和P4所在子树就行而无需遍历P1,P2.(有个问题,P3、P4的I在哪里存着?图中P1-P4都是节点P的entry,P中有I表示4个entry的MBB,但是entry中是否有各自指向的节点的I呢?)
插入
R树的插入操作也同B树的插入操作类似。当新的数据记录需要被添加入叶子结点时,若叶子结点溢出,那么我们需要对叶子结点进行分裂操作。显然,叶子结点的插入操作会比搜索操作要复杂。插入操作需要一些辅助方法才能够完成。
插入操作的伪代码:(疑问:entry的数据结构是啥?)
【Function:Insert】
描述:将新的记录条目E插入给定的R树中。
- I1:[为新记录找到合适插入的叶子结点]开始ChooseLeaf方法选择叶子结点L以放置记录E。
- I2:[添加新记录至叶子结点] 如果L有足够的空间来放置新的记录条目,则向L中添加E。如果没有足够的空间,则进行SplitNode方法以获得两个结点L与LL,这两个结点包含了所有原来叶子结点L中的条目与新条目E。
- I3:[将变换向上传递] 开始对结点L进行AdjustTree操作(调整I的大小,如果L的I变了,其父节点的I肯定也变化了),如果进行了分裂操作,那么同时需要对LL进行AdjustTree操作。
- I4:[对树进行增高操作] 如果因为L结点的分裂向上传播导致了根结点的分裂,那么需要创建一个新的根结点,并且让它的两个孩子结点分别为原来那个根结点分裂后的两个结点。
选择合适的叶子节点以插入新的entry的伪代码:
【Function:ChooseLeaf】
描述:选择叶子结点以放置新条目E。
- CL1:[Initialize]设置N为根结点。
- CL2:[叶子结点的检查] 如果N为叶子结点,则直接返回N。
- CL3:[选择子树] 如果N不是叶子结点,则遍历N中所有条目,找出添加E.I时扩张最小的条目,并把该条目指向的结点定义为F。如果有多个这样的条目,那么选择面积最小的条目指向的节点。
- CL4:[下降至叶子结点] 将F置为新的N,从CL2开始重复操作。
叶子节点中插入entry后可能导致I发生变化,而后可能会影响到其父节点直到根节点的I,其调整向上传导过程伪代码:
【Function:AdjustTree】
描述:叶子结点的I的改变向上传递至根结点以改变各个矩形。
- AT1:[初始化] 将N为当前要处理的叶子节点L。
- AT2:[检验是否完成] 如果N为根结点,则停止操作。
- AT3:[调整父结点条目的最小边界矩形] 设P为N的父节点,EN为指向在父节点P中指向N的条目。调整EN.I以保证所有在N中的矩形都被恰好包围。(从这里看出,entry中是包含了两项的,1个是ptr指向子节点,一个是I,而node中只有这些entry,并没有单独的一个I来表示所有entry的I的MBB)
- AT4:[向上传递结点分裂] 如果N有一个刚刚被分裂产生的结点NN,则创建一个指向NN的条目ENN。如果P有空间来存放ENN,则将ENN添加到P中。如果没有,则对P进行SplitNode操作以得到P和PP。
- AT5:[升高至下一级] 如果N等于L且发生了分裂,则把NN置为PP。从AT2开始重复操作。
- 有足够的空间插入的情况,由于插入的x所在的区域P2的数据条目仍然有足够的空间容纳条目x,且x的区域面积即MBR也位于区域P2之内,所以这种情况下,我们认为P2拥有足够的插入空间(同时P2的范围不需要调整,因此P2 的父节点不需要处理)。

- 需要增大MBR的插入情况,由于插入的y所在的区域P2的数据条目仍然有足够的空间容纳条目y,但是y的区域面积即MBR并不完全位于P2的区域之内,因此我们在插入数据y后会导致P2区域的相应扩大,这个时候因为P2没有父节点,所以不用处理,如果P2有父节点,则还需要更新父节点中指向P2的那个entry的I,同时计算更新后的I是否会引起上一层节点中的entry 的I发生变化。

- 需要进行分裂的插入情况,由于插入的w所在的区域P1的数据条目已经没有足够的空间容纳条目w,因为假设我们定义R树的阶m=4,而区域P1已经容纳了四个条目「A,B,C,K」了,插入w后孩子数为5,以及超过m=4了,所以要进行分裂操作,来保证树的平衡性。

最低0.47元/天 解锁文章
完全解析及Java实现&spm=1001.2101.3001.5002&articleId=116040426&d=1&t=3&u=141c0a6858844f0eb6f13d96aa941ad4)
1873

被折叠的 条评论
为什么被折叠?



