图形遍历效率低?试试 R 树

大家好,我是前端西瓜哥。

今天我们来看看 R 树是什么?以及它为什么能够提高图形的检索速度。

R 树(R-tree)是一种 空间索引技术,能够是从大量的节点中,快速找到特定范围的元素集合,而不用一个不落地遍历所有节点

思路和其他索引算法(比如 B 树、跳表)有点像,但 R 树针对的是高维数据的查询 。R 树的 “R” 指的是矩形(Rectangle)。

举个具体的例子,假设有一张地图,上面有几百万个节点,要快速找某个位置半径 2 公里的所有餐馆的信息。

低效的做法是遍历这几百万的节点的位置,判断距离是否小于 2 公里。

但如果用上索引技术,比如 R 树,我们就能利用索引去 空间换时间,快速拿到特定范围的节点超集,比如几千个。

接着只需要遍历这几千个节点去判断符合条件的节点就可以了,而不需要完完整整遍历所有的节点。

除此之外还可以:

  1. 快速检索平面中和选区矩形相交的二维图形;
  2. 在数据库中快速找出多维度的产品,比如价格、库存、过期时间在特定范围的商品。

R 树的数据结构

下面看一下在图形编辑器的一个场景。

我们构建了一棵图形树,图形树的图形有位置、宽高等属性,并渲染在画布上。

需要实现选择功能,绘制一个矩形选区,使和该选区矩形相交的图形高亮。

为实现这个能力,我们计算图形树上的每个图形的包围盒:一个用 minX,minY、maxX、maxY 表达的一个矩形,它刚好包围住图形。

包围盒的作用是简化碰撞算法,一些复杂的图形,比如贝塞尔曲线,如果要严格意义上判断碰撞,是要进行复杂的计算的,在有大量图形的场景下,性能非常糟糕。

所以业内常用矩形包围盒的碰撞来简化,这种算法非常简单高效,可直接用来替代原本复杂精细的碰撞检测,或是在进行复杂碰撞算法前先做一层过滤,避免无谓的复杂运算。

// 矩形是否相交
function intersects(a, b) {
  return b.minX <= a.maxX &&
         b.minY <= a.maxY &&
         b.maxX >= a.minX &&
         b.maxY >= a.minY;
}

这个包围盒特点,就很适合拿来应用 R 树来提高图形树的 检索效率。

R 树的数据结构是一个平衡树。

和其他索引树类似,R 树的叶子节点是数据节点,保存有图形信息和它的最小包围矩形(MBR)

最小包围矩形其实就是包围盒。

结构大概类似这样:

{
  minX: 20,
  minY: 40,
  maxX: 30,
  maxY: 50,
  // 保存图形数据,比如图形对象 id,或图形对象本身
  data: {}
};

R 树会将距离相近的数据节点收集起来,并放到同一个父节点下。这个父节点是 索引节点,不会保存图形信息,但会记录子节点的合并的包围盒数据。

父节点如果多了,也会把它们收集起来,放到一个新的父节点下。

这样就形成了一个树的结构。

实际生产环境,推荐使用一个名为 RBush 的高性能 NPM 库。

代码用法示例:

import RBush from "rbush";

// 创建一个 R 树实例
const tree = new RBush();

// 也可以指定一个索引节点最多有几个子节点,默认是 9 个
const tree2 = new RBush(16);

R 树的检索

检索的过程如下:

提供一个选区矩形,从根节点开始,往下递归查找判断选取矩形是否和当前节点矩形相交。

  1. 若不相交,其下的节点也不会相交,该节点对应的子树就不需要继续递归了;
  2. 若相交且为数据节点(叶子节点),将其放到 result 数组;
  3. 若是包含关系,其下的所有数据节点放到 result 数组;
  4. 若相交但并不包含,则遍历其下的的子节点,重复前面的操作。

直到可能相交的节点遍历完结束,然后返回 result 数组。

RBush 的搜索写法:

const result = tree.search({
  minX: 20,
  minY: 20,
  maxX: 80,
  maxY: 70,
});

其源码实现:

class RBush {
  // ...

  search(bbox) {
    let node = this.data;
    const result = [];

    if (!intersects(bbox, node)) return result;

    const toBBox = this.toBBox;
    const nodesToSearch = [];

    while (node) {
      for (let i = 0; i < node.children.length; i++) {
        const child = node.children[i];
        const childBBox = node.leaf ? toBBox(child) : child;

        if (intersects(bbox, childBBox)) {
          // 1. 遍历到数据节点
          if (node.leaf) result.push(child);
          // 2. 索引节点
          // 2.1. 包含关系,索引节点下的所有数据节点都加进 result
          else if (contains(bbox, childBBox)) this._all(child, result);
          // 2.2. 相交不包含关系,继续判断相交
          else nodesToSearch.push(child);
        }
      }
      node = nodesToSearch.pop();
    }

    return result;
  }
  
  _all(node, result) {
    const nodesToSearch = [];
    while (node) {
      if (node.leaf) result.push(...node.children);
      else nodesToSearch.push(...node.children);

      node = nodesToSearch.pop();
    }
    return result;
  }
}

R 树的更新

1、初始化

在图形编辑器初始化的时候,我们要计算图形树所有图形的包围盒,然后插入到 R 树上。

RBush 插入单个节点的写法:

const item = {
  minX: 20,
  minY: 40,
  maxX: 30,
  maxY: 50,
  graphId: '123',
};

tree.insert(item);

支持批量插入节点,RBush 针对批量添加做了优化,效率比单个插入更高。

tree.load([item1, item2, /* ... */]);

2、更新

R 数作为索引数据,是要实时更新。

为此,我们需在每次图形物理属性改变的时候,重新计算包围盒,并更新 R 树。

tree.remove(item);
tree.insert(newItem);

四叉树(Quadtree)

还有一种同样可以减少遍历节点数量的算法,叫做 四叉树(Quadtree)碰撞检测

四叉树将视口界面分割成多个区域,每个区域记住自己包含了哪些图形。

然后移动目标图形时,判断它落在哪个区域,取出所在区域的图形,这些图形集合就是和目标图形发生碰撞图形的超集。

当一个区域的图形数量过多时,又会进行分裂,再次分成 4 个区域。

四叉树详细讲解可以看我的这篇文章:

《快速检索碰撞图形:四叉树碰撞检测》

四叉树更适合图形均匀分布的场景,如果不均匀,会产生大量空节点,且查询效率会降低。

R 树除了处理二维,还可以处理更高维度的数据,相比四叉树更适合范围查询。

结尾

我是前端西瓜哥,欢迎关注我,学习更多图形知识。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/223385.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Verilog 入门(八)(验证)

文章目录 编写测试验证程序波形产生值序列重复模式 测试验证程序实例从文本文件中读取向量实例&#xff1a;时序检测器 测试验证程序用于测试和验证设计方法的正确性。Verilog 提供强有力的结构来说明测试验证程序。 编写测试验证程序 测试验证程序有三个主要目的&#xff1a;…

JNPF——强大、高效、易学的低代码开发工具

目录 1.什么是低代码 2.什么是JNPF? 3.推荐JNPF的理由 4.小结 你是否为编程世界的各种挑战感到头痛&#xff1f;想要以更高效、简单的方式开发出专业级的项目&#xff1f;JNPF低代码工具正是你苦心寻找的产品&#xff01;它是一款专为稍微懂一点点编程思想的入门级人员设计…

vue elementUI 上传非空验证

<el-form-item label"照片" prop"staffImg"><template v-slot:label><span v-show"!rules.staffImg[0].required"style"color: #ff4949;margin-right: 4px;">*</span><span>照片</span></temp…

【动手学深度学习】(六)权重衰退

文章目录 一、理论知识二、代码实现2.1从零开始实现2.2简洁实现 【相关总结】 主要解决过拟合 一、理论知识 1、使用均方范数作为硬性限制&#xff08;不常用&#xff09; 通过限制参数值的选择范围来控制模型容量 通常不限制偏移b 小的意味着更强的正则项 使用均方范数作为柔…

深入理解TDD(测试驱动开发):提升代码质量的利器

在日常的软件开发工作中&#xff0c;我们常常会遇到这样的问题&#xff1a;如何在繁忙的项目进度中&#xff0c;保证我们的代码质量&#xff1f;如何在不断的迭代更新中&#xff0c;避免引入新的错误&#xff1f;对此&#xff0c;有一种有效的开发方式能帮助我们解决这些问题&a…

CSS中区分行高,行间距

行高&#xff08;line height&#xff09; —文字占有的实际高度 —使用line-height来设置行高 行高类似于我们上学单线本&#xff0c;单线本是一行一行&#xff0c;线与线之间的距离就是行高&#xff0c;控制文字行与行之间的距离&#xff0c; 网页中的文字实际上也是写在一个…

14. 二叉树遍历

从物理结构的角度来看&#xff0c;树是一种基于链表的数据结构&#xff0c;因此其遍历方式是通过指针逐个访问节点。然而&#xff0c;树是一种非线性数据结构&#xff0c;这使得遍历树比遍历链表更加复杂&#xff0c;需要借助搜索算法来实现。 二叉树常见的遍历方式包括层序遍…

0基础学java-day14

一、集合 前面我们保存多个数据使用的是数组&#xff0c;那么数组有不足的地方&#xff0c;我们分析一下 1.数组 2 集合 数据类型也可以不一样 3.集合的框架体系 Java 的集合类很多&#xff0c;主要分为两大类&#xff0c;如图 &#xff1a;[背下来] package com.hspedu.c…

Domino多Web站点托管

大家好&#xff0c;才是真的好。 看到一篇文档&#xff0c;大概讲述的是他在家里架了一台Domino服务器&#xff0c;上面跑了好几个Internet的Web网站&#xff08;使用Internet站点&#xff09;。再租了一台云服务器&#xff0c;上面安装Nginx做了反向代理&#xff0c;代理访问…

matlab实践(十):贝塞尔曲线

1.贝塞尔曲线 贝塞尔曲线的原理是基于贝塞尔曲线的数学表达式和插值算法。 贝塞尔曲线的数学表达式可以通过控制点来定义。对于二次贝塞尔曲线&#xff0c;它由三个控制点P0、P1和P2组成&#xff0c;其中P0和P2是曲线的起点和终点&#xff0c;P1是曲线上的一个中间点。曲线上…

前端JavaScript入门-day08-正则表达式

目录 介绍 语法 元字符 边界符 量词 字符类&#xff1a; 修饰符 介绍 正则表达式&#xff08;Regular Expression&#xff09;是用于匹配字符串中字符组合的模式。在 JavaScript中&#xff0c;正则表达式也是对象&#xff0c;通常用来查找、替换那些符合正则表达式的…

Distilling the Knowledge in a Neural Network(2015.5)(d补)

文章目录 Abstract1 Introduction2 Distillation2.1 Matching logits is a special case of distillation Results 论文链接 Abstract 提高几乎所有机器学习算法性能的一种非常简单的方法是在相同的数据上训练许多不同的模型&#xff0c;然后对它们的预测进行平均[3]。不幸的是…

Node.js安装和下载(保姆级教程,别再再说你不会了)

1.浏览器搜索node.js 2.打开官网&#xff08;选择Other Download&#xff09; ​ 3.根据你的计算机版本选择 4.找到你下载的程序&#xff08;双击打开&#xff09; 5.双击后的效果如下&#xff1a; 6.继续下一步 7.选择安装路径然后下一步 8.然后继续下一步 9. 直接下一步&am…

P6 Linux 系统中的文件类型

目录 前言 ​编辑 01 linux系统查看文件类型 02 普通文件 - 03 目录文件 d 04 字符设备文件 c 和块设备文件 b 05 符号链接文件 l 06 管道文件 p 07 套接字文件 s 总结 前言 &#x1f3ac; 个人…

数据增强改进,实现检测目标copypaste,增加目标数据量,提升精度

🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv8:我的专业笔记与技术总结   -- YOLOv8轻松上手, 适用技术小白,文章代码齐全,仅需 …

postgresql自带指令命令系列二

简介 在安装postgresql数据库的时候会需要设置一个关于postgresql数据库的PATH变量 export PATH/home/postgres/pg/bin:$PATH&#xff0c;该变量会指向postgresql安装路径下的bin目录。这个安装目录和我们在进行编译的时候./configure --prefix [指定安装目录] 中的prefix参…

consistency model

Consistency is All You Need - wrong.wang什么都不用做生成却快了十倍其实也并非完全不可能https://wrong.wang/blog/20231111-consistency-is-all-you-need/[学科基础] 从布朗运动到扩散模型采样算法 - 知乎引言 扩散模型是近年来新出现的一种生成模型&#xff0c;很多工作将…

现货白银简单介绍

在贵金属投资领域&#xff0c;现货白银是当前国际上最为流行、交投最为活跃的白银投资方式&#xff0c;其交易市场遍布全球&#xff0c;包括伦敦、苏黎世、纽约、芝加哥及香港等主要市场&#xff0c;是一种以杠杆交易和做市商的形式进行的现货交易。 现货白银可以说是当下交易模…

Python (二) 读写excel文件

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

1996-2021年世界各国WGI全球治理指标:政治稳定、制度控制、国家治理、控制腐败、自由指数数据

1996-2021年世界各国WGI全球治理指标&#xff1a;政治稳定、制度控制、国家治理、控制腐败、自由指数数据 1、时间&#xff1a;1996-2021年 2、指标&#xff1a;Voiceand Accountability、Political Stability No Violence、Government Effectiveness、Regulatory Quality、R…