R树详解

B的搜索本质上是一维区间的划分过程,每次搜索节点所找到的子节点其实就是一个子区间。R 树是把 B 树的思想扩展到了多维空间,

采用了 B 树分割空间的思想,是一棵用来存储高维数据的平衡树。

          

对于一棵 R 树,叶子节点所在层次称为 Level1,根节点所在层次称为 Levelh。一棵 R 树满足如下性质:

    1)除根结点之外,所有非根结点包含有 mM 个记录索引(条目)。根结点的记录个数可以少于 m。通常 m=M2

    2)每一个非叶子结点的分支数和该节点内的条目数相同,一个条目对应一个分支。所有叶子结点都位于同一层,因此 R 树为平衡树。

    3)叶子结点的每一个条目表示一个点。

    4)非叶结点的每一个条目存放的数据结构为:(I,childpointer)childpointer 是指向该条目对应孩子结点的指针。I 表示

       一个 n 维空间中的最小边界矩形(minimumboundingrectangleMBR),I 覆盖了该条目对应子树中所有的矩形或点。

          

       两个黑点保存在一个叶子节点的两个条目中,恰好框住这两个条目的矩形表示为:I=(I0,I1)其中 I0=(a,b),I1=(c,d),也就

       是最小边界矩形是用各个维度的边来表示,在三维空间中那就是立方体,用 3 条边就可以表示了。

下面构建一棵 R 树。如下左图,理论上,点可以任意组合成叶节点,只要 MBR 包含它子树中的所有点。特别是 MBR 可以重叠。下面

右图是另一种组合建立的 R 树。

    

哪种分组更好呢?一般分组的原则就是最小化每个 MBR 矩形,这样查询的时候发生的相交情况会越少,查询的分支就越少,查询效率越高。

 

R-tree 查询

介绍查询之前,需要先了解下:如何判断两个线段或者两个矩形是否相交?

    

1. Range Query

   这种查询输入的是一个矩形所表示的范围,要求输出该范围内的所有点。从根节点开始,通过判断目标矩形和节点内的每一个条目对应的

   矩形是否相交来选择下一步查询的节点,如果有多个条目都相交,那对应的各个分支都得查。到达叶子节点后,就判断该叶子节点的每一

   个条目是否在查询区域内即可。

   现在想查询在矩形 [5,8.5],[4,7.5] 内的所有点,即下图中的阴影矩形,设该矩形为 q首先判断 E6.I,E7.Iq 是否相交,发E7.I

   与 q 相交,于是通过 E7 中的 childpointer 指针到达孩子节点,再判断 E4.I,E5.Iq 是否相交,发现 E4.Iq交,接下来

   就到达叶子节点了,然后判断每个点是否在矩形 q 内即可,如果在则输出。

   

2. Nearest Neighbor Query 

   这种查询输入的是一个点,要求输出离这个点最近的 k 个点,所以又叫 kNN 查询。首先需要知道如何定义一个点到一个矩形的最

   短距离,记点 q 到矩形 E 的最短距离为 mindist(q,E)。规定:以 q 为圆心,与 E 有交点的最小圆的半径就是 mindist(q,E)。 

   kNN 查询有两种算法,下面一一介绍。

   1)Depth-First NN Algorithm

      假设 k=1,即查找距 q 最近的一个点。

      输入一个点 q,从根节点开始,计算 q 到每个条目对应矩形的最短距离,即 mindist(q,E6.I),mindist(q,E7.I),计算完后从小到大排序。

      因为 mindist(q,E6.I)<mindist(q,E7.I),所以来到 E6 的孩子节点,同样计算 mindist(q,E1.I),mindist(q,E2.I),mindist(q,E3.I)

      并从小到大排序,如下右图。

           

      qE1.IE2.I 有相同的最短距离,于是随机选择一个,这里选择 E1,于是来到 E1 的叶子节点。计算点 a,b,cq 点的距离,

      保存距离 q 最近的那个点的距离,显然 a,q 距离最近,这个距离记为 r,这个 r 只是当前搜索结果。接下进行回溯,回到上一个节点,注意

      q 到所经过节点每个条目的最短距离都已经算好并升序排列,现在选择距离 q 第二近的那个条目即 E2,此时有一步很重要的剪枝操作,需要

      判断mindist(q,E2.I)r 的大小,如果 mindist(q,E2.I)>r,那显然没有必要再去搜索它的子树了,因为E2 区域中的点到 q 的距离不

      可能会比 r 小了。

      接下来回溯到根节点,因为 mindist(q,E7.I)<r,所以在 E7 区域中可能存在到 q 的距离比 r 小的点,需要搜索。来到 7 的孩子节点,

      计算 mindist(q,E4.I),mindist(q,E5.I) 并升序排列,接下来访问 E4 的孩子节点,发现 q,h,的距离小于 r,于是用新的最短距离更新 r

          

      然后再回溯,在已升序排列的数组里取下一个节点,判断是否剪枝。。。。

      当 k=2 时,过程也是一样的,只不过要保存两个距离(最短和次短),并使用次短距离进行剪枝。过程如下:

      a. Root => child node of E6 => child node of E1 => find {a, b} here

            b.   Backtrack to child node of E6 => child node of E2 (its mindist < dist(q, b)) => update the result to {a, f}

            c.   Backtrack to child node of E6 => child node of E3 => backtrack to the root => child node of E7 => child node of E4 => update the result to {a, h}

            d.   Backtrack to child node of E7 => prune E5 => backtrack to the root => end.

              

   2)Best-First Algorithm

      这个算法需要维护一张点 q 到所访问条目的最短距离表,这张表按升序排列,直接来看一下搜索过程。

      访问根节点的时候计算 mindist(q,E6.I),mindist(q,E7.I),分别保存为位置 01。每一次迭代都访问第一个元素。计算 q 

      到新结点每个条目的最短距离,然后更新表并重新排序。整个过程如下图所示,从左往右,从上到下阅读。

         

                 

      此时就可以得到点 hq 的距离最短。如果 k=2,则继续搜索。   

             

 

R-tree 插入

插入的可以是一个点,也可以说是一个 R 树。

1. 插入一个点 p

   设根节点为 N遍历 N 中所有条目,找出添加该点后 E.I 扩张最小的条目(代价最小),并把该条目对应的孩子节点定义为 F。如果有多

   个这样的条目,那么选择面积最小的条目。将 N 设为 F,开始上述重复操作直到找到一个叶子节点。

   如果选择出来的叶子节点有足够的空间来放置点 p,则直接添加一个条目就可以了。如果没有足够的空间,即插入后该叶子节点含有的条目高

   于 M则需要进行节点分裂。分裂方法如下:

   将插入 p 后的叶子节点分裂为两个结点 LLL,这两个结点包含了原来叶子节点 L 中的所有条目与新条目。

   将 N 设为 L,设 PN 的父节点,EN 为父节点 P 中指向 N 的条目。调整 EN.I 以保证所有在 N 中的条目都被恰好包围。

   创建一个指向 NN 的条目 ENN。如果 P 有空间来存放 ENN,则将 ENN 添加到 P 中。如果没有,则对 P 进行分裂操作得到 P

   和 PPNPNNPP,按相同的规则继续向上层传播。

   如果结点分裂,且该分裂向上传播导致了根结点的分裂,那么需要创建一个新的根结点,并且让它的两个孩子结点分别为原来那个根结点分裂

   后的两个结点,R 树增高,程序结束。

   举个例子,假设 M=3,把点 p(3,5) 插入到 R 树中,根据最小调整代价原则,最终选择将点 p 插入到 E3 中。如下图所示。

   

   接下来继续插入点 s(2,5),点 s 应该被插入到 E3 指向的叶子节点中,但是插入后该叶子节点的条目变成了 4(>M),于是需要进行节点分裂。

   将 k,s,j,p 分裂为两个叶子节点,分别包含 k,sj,p,并调整 E3.I,然后为新节点(包含 j,p)创建一个新条目 E8

      

   但是 E8E1,E2,E3 放在一起又会时节点条目数超过 M,所以需要再进行节点分裂,根据最小 MBR 原则,将 E2,E3 放到一起,E1,E8

   放到一起,调整 E6.I 的大小使其适配节点 E2,E3,为节点 E1,E8 创建一个新的条目 E9,插入到根节点,如下图所示:

   

2. 插入一棵 R

   插入一棵 R 树其实插入的是 R 树根节点所有条目所表示的矩形。和插入一个点一样,在选择条目的时候都是采用扩张最小代价原则。

   不同的是:插入一个点,最终是一定会插入到叶子节点中,但是插入 R 树的时候,由于 R 树本身有高度,假设它的高度为 H,那么

   该它会被插入到 LevelH 层。

   举个例子,向树中插入 E2 这课 R 树,因为它的高度为 2,所以 E2 会被插入到 Level2

      

 

R-tree 删除

R 树中删除一个点,首先需要找到该点所在的叶子节点,通过判断点是否在条目所对应的矩形区域内来选择分支,直到找到叶子

节点,判断所要删除的点是否在该叶子节点内,如果在则删除,并调整父节点对应题目的矩形。删除之后,如果叶子节点剩余条目数过

少,即小于要求的最小值 m则需要进行调整,令 N 为条目数低于下限的叶子节点,调整步骤如下。

初始化一个用于存储被删除结点包含的条目的链表 Q。令 PN 的父结点,ENP 结点中存储的指向 N 的条目。

因为 N 含有条目数少于 m,于是从 P 中删除 EN,并把结点 N 中的条目添加入链表 Q 中,然后输出节点 N

往上层走,令 N 等于 P,继续进行下溢判断,如果下溢,则将该节点中的每个条目加到 Q 中,删除该结点和父节点中对应条目。

如果没有下溢,不要忘记调整祖父节点对应条目的矩形形状。

所有在 Q 中的结点中的条目需要被重新插入。原来属于叶子结点的条目可以使用 Insert 操作进行重新插入,而那些属于非叶子结点

条目必须插入删除之前所在层的结点,以确保它们所指向的子树还处于相同的层(相当于插入一棵 R 树)。

举个例子,删除点 k(1,6),从根节点开始找,可以找到 E3,将其删除,但是下溢了。具体过程如下图:

       

       

接下来将 Q 中的项重新插入到 R 树中。

       

 

posted @   _yanghh  阅读(6750)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示