概览
为什么需要R树
R树基于B树分段查询的思想。如果说B树更多的是在一维空间下,那么R树则是可以工作在更高维的空间下。我们拿美团举例,假如说我们想要知道两公里范围内的可配送商家都有哪些?
那么如果我们拿B树存储,其对于美团的商家的经度需要建立一棵B树,而对于纬度一样也需要建立一颗B树。我们这里拿坐标表示,假设我们要寻找[5,5]~[8,8]
这个范围的所有商家,寻找过程如下:
首先我们要要寻找经度在[5,8]
范围的商家,得到结果 E、F、G、H
之后我们根据纬度B树得到结果:K、A、E、C
最终聚合两次查找的结果得到两公里内的商家为E
这只是二维的情况下,如果是多维,那么必然要经历多个这样的过程。所以应运而生了R树这种数据结构。
R树
1984年,加州大学伯克利分校的Guttman发表了一篇题为“R-trees: a dynamic index structure for spatial searching”的论文,向世人介绍了R树这种处理高维空间存储问题的数据结构。
一棵 R 树满足如下性质:
-
1)除根结点之外,所有非根结点包含有 m 至 M 个记录索引(条目)。根结点的记录个数可以少于 m。通常 m=M/2。
-
2)每一个非叶子结点的分支数和该节点内的条目数相同,一个条目对应一个分支。所有叶子结点都位于同一层,因此 R 树为平衡树。
-
3)叶子结点的每一个条目表示一个点。
-
4)非叶结点的每一个条目存放的数据结构为:(I,child−pointer)。child−pointer 是指向该条目对应孩子结点的指针。I 表示一个 n 维空间中的最小边界矩形(minimumboundingrectangle,即 MBR),I 覆盖了该条目对应子树中所有的矩形或点。
我们还是拿这个例子举例,看R树是如何解决这个问题的。首先我们给出每个商家的二维坐标:
这样之后我们就可以开始划分区域范围了,首先可以分为两个大的区域R12和R13 。其中R12的范围是:[0,5]~[10,16]
,R2的范围是:[7,0]~[16,7]
。这样在R1的范围内就有A、B、C、D、E、F、G、H、I;在R2的范围内有K、M、N、T、Z。
虽然看着上面的十分复杂,但是我们将其用R树的树形结构表示一下便如下:
这就是R树的非叶子节点结构图,而叶子节点就是具体代表商家的A、B、C
这样我们搜索[5,5]~[8,8]
区域的时候就会去从根节点R12和R13开始比对,很明显其是在R12[0,5]~[10,16]
这个范围的。之后我们再次判断其是在R10[0,4]~[6,9]
这个范围,之后不断迭代缩小范围最终查找到区域R5
,这个时候读取区域R5
范围内的节点,得到 E 这个答案。
这就是使用R树来解决这个问题,其比B树大大减少了搜寻的这个过程。对于更高维的问题则可以建造更高维的数据坐标系。
R树操作详解
R树构建
首先还是刚刚图,我们把每两个点之间采用MBR框起来,这里尽量的最小化每个 MBR 矩形,这样查询的时候发生的相交情况会越少,查询的分支就越少,查询效率越高。
我们这里采用两两划分一组,同时划分最近的两个点为一个叶子节点。
这里,我们其实构建的就是叶子节点。其用C++代码就可以表示为如下的结构:
struct Element
{
int x;
int y;
};
struct LevelNode
{
int area_x_begin;
int area_x_end;
int area_y_begin;
int area_y_end;
Element* elem1;
Element* elem2;
}:
之后我们将这些叶子节点划分至非叶子节点之中,划分规则还是采用两两划分一组,同时划分最近的两个点为一个叶子节点。迭代这个过程最终形成如下图:
非叶子节点用C++代码表示就如下:
struct NotLevelNode
{
int area_x_begin;
int area_x_end;
int area_y_begin;
int area_y_end;
LevelNode* level_node;
}:
KNN查询
刚刚我们介绍的是范围查询,现在来介绍一下KNN查询。假设我们有一个点X,我们要查询距离其最近的一个点:
首先,我们计算这个点到根节点R12和R13哪个近一些:
mindest(X,R12) = 2;
mindest(X,R13) = 5;
因为我们这里距离R12比较近,所以来到R12的child Tree:
这个时候我们再次计算X和R9、R10的距离:
mindest(X,R9) = 6;
mindest(X,R10) = 2;
这里最近的是R3,所以我们接着计算R12 的child Tree:
这个时候距离R10区域的点比较近,我们继续计算和R2以及R5区域的距离:
mindest(X,R2) = 3;
mindest(X,R5) = √8;
这个时候距离R5区域更近,由于R5没有child Tree了,我们就可以读取R5区域的点,之后和这个要查找的点计算就可以。这个例子的计算结果就是 L 点。