KDTree 简单原理与实现

KDTree 可视化空间划分在这里插入图片描述

介绍

K-D树是一种二叉树的数据结构,其中每个节点代表一个k维点,可用于组织K维空间中的点,其中K通常是一个非常大的数字。二叉树结构允许对多维空间中的点进行非常有效的搜索,包括最近邻搜索和范围搜索,树中的每个非叶节点都充当一个超平面,将空间分割为两个区域。 这个超平面垂直于所选的轴,该轴与K维相关联。

而区域在划分时,有不同的选择轴的策略

划分轴策略

1.按深度划分

说明:

重复地循环K维中的每一个轴,并选择沿着它的中点来划分空间。例如,对于具有和轴的二维点xy,我们首先沿着x-轴进行分割,然后是y-轴,然后再次是-x轴,(即偶数深度的x-轴分割,奇数深度y-轴分割)以这种方式继续,直到所有点都被考虑在内:

范例:

已有位置数据:Vector2[] position = {A,B,C,D,E,F} ,将其进行空间划分

  • 第一次分割:深度 0 ,Y 轴分割 A
  • 第二次分割:深度 1 ,X 轴分割 B
  • 第三次分割:深度 2 ,Y 轴分割 C
  • 第四次分割:深度 2 ,Y 轴分割 D
  • 第五次分割:深度 1 ,X 轴分割 E
  • 第六次分割:深度 2 ,Y 轴分割 F
在这里插入图片描述在这里插入图片描述
这种方式会有一种问题 --二叉树不平衡(当树的深度很深时很影响效率,就必须将树进行重新排序)

2.以K维中的最大包围边划分

  • 这里还有不同的策略,有的是按最大密度边有的是按最长边,这里的是按最长边;

每一次分割空间以按内部元素的包围盒最大的那一边的中点位置进行分割,直至分割到一个区域内只有一个对象

已有位置数据:Vector2[] position = {A,B,C,D,E,F} ,将其进行空间划分

  • 第一次分割:(A,B,C,D,E,F)其包围盒的X边 > Y边,以(E.x-B.x)/2的位置划分为 ab
  • 第二次分割:(B,C,D)其包围盒的X边 < Y边,以(D.y-C.x)/2的位置划分为 cd
  • 第三次分割:(B,C)其包围盒的X边 < Y边,以(B.y-C.x)/2的位置划分为 ef
  • 第四次分割:(A,E,F)其包围盒的X边 < Y边,以(E.y-F.x)/2的位置划分为 gh
  • 第五次分割:(A,F)其包围盒的X边 < Y边,以(A.y-F.x)/2的位置划分为 ij
这种二叉树就很平衡,因为每次划分后都需要将<位置数据>进行重新排序,而树则不用管;在左图中可直接看出每次划分都将原始数据进行了拆分,直至最后拆分到一个区域只有一个对象时停止

算法实现

这里采用的是第二种空间分割策略 【以K维中的最大包围边划分】

这里有两个问题要说明一下

1. “位置数据”的多少与树的总节点数的关系

因为位置数据会实时变动(数组长度不变,仅是里面的数据元素值在变化,暂不考虑动态长度的数组在二叉树中的求解),所以二叉树也很会随之频繁的重新构建,那么构建就必须足够的轻量化(无CG),进而需要一个固定的数组容器来存放树节点

在这里插入图片描述
因为我们设定的是最极端的情况,一个叶子节点所对应的空间内最多X个对象(X == 1)

所以可用一个满的二叉树去计算,那么其总节点数与叶子节点比关系为 2N-1 :N

那么存放树节点的容器长度就为“位置数据”长度的2倍-1

2. 一个叶子节点所对应的空间内最多X个对象

这一点如果真的采用 X == 1 的方式那可就太浪费了,因为对象数可能会很密集,放在同一个区域内的话岂不是能更快的查找?所以 X 的值的大小的增加会减少树的深度,那么树查找也就快了;但X 值的增加也会同时增大空间分割的区域导致不能更快的定位位置,所以必须要找到一个平衡;

这和分割策略有很大的影响,最理想的分割情况就是按区域内的成员密度进行分割,这样的二叉树与叶子内对象分布最合理(但更复杂,暂不谈论)

/树深度叶子内对象
X 增大减小(树遍历加快)增大(对象定位减慢)
X 减小增大(树遍历减慢)减少(对象定位加快)

代码示例

namespace Test.KDTree
{public class KDTree{//树节点 --用于分割空间与记录容纳对象private struct AgentTreeNode{// [begin,end) 是代理容器内的一段区间范围,表示该节点内的对象internal int begin;internal int end;internal int left;		//左分支索引internal int right;		//右分支索引// 该节点的 AABB 包围盒范围,包围的就是 [begin,end] 成员的最大范围internal Vector2 min;internal Vector2 max;public float LengthX => max.x - min.x;public float LengthY => max.y - min.y;public float CenterX => (max.x + min.x)*0.5f;public float CenterY => (max.y + min.y)*0.5f;//返回该节点下的对象个数public int Count => end - begin;//返回position距离包围盒的距离平方,如果在包围盒内(含边框)返回0;public float SqrDisBounds(Vector2 p){return sqr(Math.Max(0.0f, min.x - p.x)) + sqr(Math.Max(0.0f, min.y - p.y)) + sqr(Math.Max(0.0f, p.x - max.x)) + sqr(Math.Max(0.0f, p.y - max.y));}float sqr(float scalar){return scalar * scalar;}}//二叉树要分割的对象代理(要与游戏中的对象解耦)public struct Agent{public int id;					//游戏中的对象IDpublic Vector2 position;		//游戏中的对象位置}//节点内容纳的最大对象数private const int MAX_LEAF_SIZE = 10;private Agent[] agents_;							//代理对象容器private AgentTreeNode[] agentTree_;					//代理节点容器//通过游戏中的对象数量初始化容器大小public void InitAgentCapacity(int count){agents_ = new Agent[count];agentTree_ = new AgentTreeNode[2 * agents_.Length-1];}//添加代理public void AddAgent(int id){agents_[id].id = id;}//构建二叉树public void buildAgentTree(){//更新对象代理成员的位置数据for (int i = 0; i < agents_.Length; ++i){agents_[i].position = getAgentPosition(agents_[i].id);}buildAgentTreeRecursive(0, agents_.Length, 0);}//递归构建二叉树private void buildAgentTreeRecursive(int begin, int end, int node){agentTree_[node].begin = begin;agentTree_[node].end = end;agentTree_[node].min = agentTree_[node].max = agents_[begin].position;//计算该节点的Boundsfor (int i = begin + 1; i < end; ++i){agentTree_[node].max = Vector2.Max(agentTree_[node].max, agents_[i].position);agentTree_[node].min = Vector2.Min(agentTree_[node].min, agents_[i].position);}//当现有对象大于<最大对象数>时才进行分割,也说明其不是叶子节点if (end - begin > MAX_LEAF_SIZE){//是否是垂直定向的bool isVertical = agentTree_[node].LengthX > agentTree_[node].LengthY;//定向的中间位置float splitValue = isVertical ? agentTree_[node].CenterX : agentTree_[node].CenterY;int left = begin;		//在对象容器[begin,end]内的包含范围起始索引int right = end;		//在对象容器[begin,end]内的包含范围结束索引//根据中间位置将对象容器[begin,end]进行重排序//将小于中间位置的放置在容器[begin,end]左边;将大于等于中间位置的放置在容器[begin,end]右边;while (left < right){while (left < right && (isVertical ? agents_[left].position.x : agents_[left].position.y) < splitValue) ++left;while (right > left && (isVertical ? agents_[right - 1].position.x : agents_[right - 1].position.y) >= splitValue) --right;if (left < right){Agent tempAgent = agents_[left];agents_[left] = agents_[right - 1];agents_[right - 1] = tempAgent;++left;--right;}}//获取容器[begin,end]左边(小于中间位置的对象)的数量int leftSize = left - begin;//因为与中间值比较时等于的部分放置在了右边,所以会出现左边无成员的情况(通常是有大量重叠),就让右边给左边让一个出来(其实你都全重叠到一个点上了,不让也可以,反正这时的二叉树时不可能平衡的)if (leftSize == 0){++leftSize;++left;++right;}//这里的二叉树存储结构时按照容器[begin,end]的左边数量进行决定该节点右分支的放置位置,这样在满二叉树的状态下,一个叶子节点对应一个对象agentTree_[node].left = node + 1;agentTree_[node].right = node + 2 * leftSize;//left 和 right 将容器[begin,end]划分为了两块,这里让其递归计算其自身的分块buildAgentTreeRecursive(begin, left, agentTree_[node].left);buildAgentTreeRecursive(left, end, agentTree_[node].right);}}public Func<int, Vector2> getAgentPosition;			//获取指定代理对象的位置public Action<int> call_Near;//迭代查询指定位置下给定半径中的所有对象//rangeSq 为半径的平方,为的是在计算两点位置时不用进行开根号处理public void queryAgentTreeRecursive(Vector2 position_, float rangeSq, int node){//表示其为叶子节点,可直接进行包含对象遍历if (agentTree_[node].Count <= MAX_LEAF_SIZE){for (int i = agentTree_[node].begin; i < agentTree_[node].end; ++i){if (Vector2.SqrMagnitude(agents_[i].position - position_) < rangeSq){call_Near(agents_[i].id);}}}else{//每个节点下都有两个分支,可二分查找最近的区域float distSqLeft = agentTree_[agentTree_[node].left].SqrDisBounds(position_);float distSqRight = agentTree_[agentTree_[node].right].SqrDisBounds(position_);if (distSqLeft < distSqRight){if (distSqLeft < rangeSq)	//left区域是否在半径内,right 比left大就没必要else了{queryAgentTreeRecursive(position_, rangeSq, agentTree_[node].left);if (distSqRight < rangeSq)	//left right 都在半径范围内时,right也要考虑{queryAgentTreeRecursive(position_, rangeSq, agentTree_[node].right);}}}else{if (distSqRight < rangeSq){queryAgentTreeRecursive(position_, rangeSq, agentTree_[node].right);if (distSqLeft < rangeSq){queryAgentTreeRecursive(position_, rangeSq, agentTree_[node].left);}}}}}}
}

项目包 package 导入Unity 内即可

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java毕业设计 基于SSM vue新生报到系统小程序 微信小程序

Java毕业设计 基于SSM vue新生报到系统小程序 微信小程序 SSM 新生报到系统小程序 功能介绍 学生 登录 注册 忘记密码 首页 学校公告 录取信息 录取详情 师资力量 教师详情 收藏 评论 用户信息修改 宿舍安排 签到信息 在线缴费 教室分配 我的收藏管理 我要发贴 我的发贴 管理…

【基于R语言群体遗传学】-7-遗传变异(genetic variation)

一些新名词 Continuous time: 连续时间&#xff0c;是指不间断流动的时间&#xff0c;不以单位时间形式出现。 Diffusion: 扩散&#xff0c;是指粒子从高浓度区域向低浓度区域的被动净移动。 Discrete time: 离散时间&#xff0c;是指被划分为单位的时间&#xff0c;例如每个…

动手学深度学习(Pytorch版)代码实践 -计算机视觉-48全连接卷积神经网络(FCN)

48全连接卷积神经网络&#xff08;FCN&#xff09; 1.构造函数 import torch import torchvision from torch import nn from torch.nn import functional as F import matplotlib.pyplot as plt import liliPytorch as lp from d2l import torch as d2l# 构造模型 pretrained…

Python内存优化的实战技巧详解

概要 Python是一种高级编程语言,以其易读性和强大的功能而广受欢迎。然而,由于其动态类型和自动内存管理,Python在处理大量数据或高性能计算时,内存使用效率可能不如一些低级语言。本文将介绍几种Python内存优化的技巧,并提供相应的示例代码,帮助在开发中更高效地管理内…

Banana Pi BPI-M4 Berry创建热点和设置静态IP

create_ap是一个帮助快速创建Linux上的WIFI热点的脚本&#xff0c;并且支持bridge和NAT模式&#xff0c;能够自动结合hostapd, dnsmasq和iptables完成WIFI热点的设置&#xff0c;避免了用户进行复杂的配置&#xff0c;github地址如下&#xff1a; https://github.com/oblique/…

时钟系统框图(时钟树)解析

时钟系统框图&#xff08;时钟树&#xff09;解析 文章目录 时钟系统框图&#xff08;时钟树&#xff09;解析1、时钟树2、 4个时钟源&#xff1a;$HSI、HSE、LSI、LSE$3、PLL锁相环倍频输出4、系统时钟的来源5、Enable CSS&#xff08;时钟监视系统&#xff09;6、几个重要的时…

微软与OpenAI/谷歌与三星的AI交易受欧盟重点关注

近日&#xff0c;欧盟委员会主管竞争事务的副主席玛格丽特维斯塔格(Margrethe Vestager)在一次演讲中透露&#xff0c;欧盟反垄断监管机构将就微软与OpenAI的合作&#xff0c;以及谷歌与三星达成的AI协议寻求更多第三方意见。这意味着微软与 OpenAI、谷歌与三星的 AI 交易及合作…

杰理科技AD142A语音芯片,语音玩具方案—云信通讯

语音玩具产品市场的需求量比较大&#xff0c;从前简单的发光玩具&#xff0c;到各种动作的电子玩具&#xff0c;再到如今的语音录音灯光动作玩具&#xff0c;可见玩具行业也是在不断地演变。 杰理语音芯片AD142A4的优势主要是支持录音、录变音、语音播放&#xff0c;广泛应用于…

【Android】Android基础--显式Intent和隐式Intent

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

【数据结构】常见四类排序算法

1. 插入排序 1.1基本思想&#xff1a; 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是&#xff1a;把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新的有序序列 。实际中我们…

SQL Server 2022数据库对象

《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;》图书介绍-CSDN博客 数据库对象是数据库的组成部分&#xff0c;数据表、视图、索引、存储过程以及触发器等都是数据库对象。 &#xff08;1&#xff09;数据库的主要对象是数据表&#xff0c;数据表是一…

深圳航空顶象验证码逆向,和百度验证码训练思路

声明(lianxi a15018601872) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言(lianxi a…

encrypt decrypt CA

encrypt & decrypt & CA 加密解密证书

基于java+springboot+vue实现的大学生就业需求分析系统(文末源码+Lw)233

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自…

A股继续3000以下震荡,而国外股市屡创新高,人民币反弹能带动A股吗?

今天的A股&#xff0c;让人愤愤不已&#xff0c;你知道是为什么吗&#xff1f;盘面上出现3个耐人寻味的重要信号&#xff0c;一起来看看&#xff1a; 1、今天两市一度回踩2920点&#xff0c;让股民的心都开始悬起来了。而午后市场行情有了转变&#xff0c;下跌的股票开始明显变…

在centos7上部署mysql8.0

1.安装MySQL的话会和MariaDB的文件冲突&#xff0c;所以需要先卸载掉MariaDB。查看是否安装mariadb rpm -qa | grep mariadb 2. 卸载mariadb rpm -e --nodeps 查看到的文件名 3.下载MySQL安装包 MySQL官网下载地址: MySQL :: Download MySQL Community Serverhttps://dev.mys…

c/c++ 程序运行的过程分析

c/c编译基础知识 GNU GNU&#xff08;GNU’s Not Unix!&#xff09;是一个由理查德斯托曼&#xff08;Richard Stallman&#xff09;在1983年发起的自由软件项目&#xff0c;旨在创建一个完全自由的操作系统&#xff0c;包括操作系统的内核、编译器、工具、库、文本编辑器、邮…

线性代数基础概念:矩阵

目录 线性代数基础概念&#xff1a;矩阵 1. 矩阵的定义 2. 矩阵的运算 3. 矩阵的特殊类型 4. 矩阵的秩 5. 矩阵的初等变换 6. 矩阵的特征值与特征向量 7. 矩阵的应用 8. 矩阵总结 总结 线性代数基础概念&#xff1a;矩阵 矩阵是线性代数中的另一个重要概念&#xff0…

学java的第3天 后端商城小程序工作

1.数据库的大坑 特殊字段名 ’我的图片表中有一个字段是描述我写成desc了&#xff0c;正好是mysql中的关键字 就不能使用了 2.后端编写 2.1可以把请求分开 在商品浏览页中 只显示商品的大致信息 当用户再点击其他按钮时在发出请求 2.2把请求合并 把数据整合到一起 利用ass…

Python爬虫教程第1篇-基础知识

文章目录 什么是爬虫爬虫的工作原理用途搜索引擎爬虫Robots协议HTTP的请求过程URL的含义HTTP常见请求头爬虫常用的技术 什么是爬虫 信息的交互是通过web网页、或者移动端等不同的客户端端形式进行交互&#xff0c;这个过程是一个人与网路正常的交互行为。而爬虫可以用来模拟人…