重复的四叉树


10

我正在实现一个四叉树。对于那些不知道此数据结构的人,我包括以下简短描述:

叉树是一种数据结构,在欧几里得平面中就像3维空间中的八叉树一样。四叉树的常见用途是空间索引。

总结一下它们是如何工作的,四叉树是一个集合(假设这里是矩形),具有最大容量和一个初始边界框。当尝试将元素插入达到最大容量的四叉树中时,该四叉树被细分为4个四叉树(其几何表示将比插入前的树小四倍)。每个元素根据其位置重新分配在子树中。使用矩形时的左上边界。

因此,四叉树要么是叶子,其元素数量少于其容量,要么是一棵有4个四叉树作为孩子的树(通常是西北,东北,西南,东南)。

我担心的是,如果您尝试添加重复项,可能是同一元素多次或具有相同位置的多个不同元素,则四叉树在处理边缘时存在一个基本问题。

例如,如果您使用容量为1的四叉树并将单位矩形作为边框:

[(0,0),(0,1),(1,1),(1,0)]

然后您尝试插入两次以其左上边界为原点的矩形:(或类似地,如果尝试在容量为N> 1的四叉树中将其插入N + 1次)

quadtree->insert(0.0, 0.0, 0.1, 0.1)
quadtree->insert(0.0, 0.0, 0.1, 0.1)

第一次插入不会有问题: 第一次插入

但是然后第一个插入将触发细分(因为容量为1): 第二插入,第一细分

因此,两个矩形都放在同一子树中。

然后,这两个元素将到达相同的四叉树并触发细分… 第二次插入,第二次细分

依此类推,依此类推,细分方法将无限期地运行,因为(0,0)始终位于所创建的四个子树中的同一子树中,这意味着将发生无限递归问题。

有可能有重复的四叉树吗?(如果没有,则可以将其实现为Set

我们如何解决这个问题而又不完全破坏四叉树的体系结构?


您希望它表现如何?您正在实施它,因此您必须确定哪种行为对您来说是正确的。也许每个唯一坐标都可以是该坐标处的元素列表。也许您的观点是唯一的。您知道您需要什么,而我们却没有。
没用的2014年

@Useless这是真的。但是,在该主题上必须进行了大量研究,我也不想重复发明轮子。TBH我仍然不知道这个问题是否更多地归因于SO,程序员,SE,gamedev.SE甚至是数学.SE…
Pierre Arlaud

Answers:


3

您正在实现数据结构,因此必须制定实现决策。

除非四叉树有关于唯一性的具体说明-我不知道它是否如此-这是一个实现决策。它与四叉树的定义正交,您可以根据需要选择处理它。四叉树告诉您如何插入和更新密钥,但不告诉它们是否必须唯一,或者可以附加到每个节点的内容。

制定实施决策并不是在浪费时间,至少只是首先编写您自己的实施。

为了进行比较,C ++标准库提供了一个唯一集,一个非唯一多集,一个唯一映射(本质上是一组仅由键排序和比较的键-值对)和一个非唯一多映射。它们通常都是使用相同的红黑树来实现的,并且都不会破坏体系结构,这仅仅是因为红黑树的定义与密钥的唯一性或叶节点中存储的类型无关。

最后,如果您认为对此有研究,找到它,然后我们可以进行讨论。也许有一些我忽略的四叉树不变性,或者一些其他的约束可以提高性能。


我的问题是我找不到任何说明唯一性的文档。但是,如果您看过我的示例,则可以看到,如果您多次包含相同的元素,那将是一个真正的问题。
皮埃尔·阿劳德

对于树类型的结构,是否有时还会给具有该值的节点指定一个“ count”字段,该字段仅对重复项进行递增和递减?
J特拉纳2014年

2

我认为这里有一个误解。

据我了解,每个四叉树节点都包含一个由点索引的值。换句话说,它包含三元组(x,y,value)。

它还包含4个指向子节点的指针,该指针可以为null。键和子链接之间存在算法关系。

您的插入内容应如下所示。

quadtree->insert(0.0, 0.0, value1)
quadtree->insert(0.0, 0.0, value2)

第一次插入将创建一个(父)节点并将一个值插入其中。

第二个插入将创建一个子节点,链接到该子节点,然后在其中插入一个值(该值可能与第一个值相同)。

实例化哪个子节点取决于算法。如果算法的形式为[x],并且坐标空间在[0,1)范围内,则每个子对象将跨越[0,0.5)范围,并且该点将放置在NW子对象中。

我看不到无限递归。


因此,您说的是细分时将节点重新分配给子四叉树的方式是我的实现方式有什么问题?
皮埃尔·阿洛德

也许问题在于您正在尝试将值从其所在位置(在父级中)移动到一个更好的位置(在儿童中)。确实不是这样。该值在任何位置都可以。但这确实导致了一个有趣的结果,即可以将两个相同的点放置在不同的节点中(但始终与父级和子级相关)。
david.pfx


2

我假设您正在索引大小都相同的元素,否则生命会变得复杂或缓慢,或两者兼而有之……

四叉树节点不需要具有固定的容量。该容量用于

  • 允许将每个树节点在内存或磁盘中固定为大小 –如果树节点包含一组可变大小的元素,并且您正在使用可应对的空间分配系统,则不需要这样做。(例如内存中的java / c#对象。)
  • 决定何时拆分节点。
    • 您可以重新定义规则,以便如果节点包含多个“ n”个区域元素,则该节点将被拆分,其中,根据元素的位置定义区域。
    • 或使用“ 复合元素”,因此,如果在同一位置有乘法元素,则引入一个新元素,其中包含这些乘法元素的列表。

2

当您处理空间索引问题时,我实际上建议从空间散列或我个人最喜欢的地方开始:普通的旧网格。

在此处输入图片说明

...并先了解其缺点,然后再转向允许稀疏表示的树结构。

明显的缺点之一是,您可能会浪费很多空单元格上的内存(尽管体面实现的网格每个单元格不需要超过32位,除非您实际上要插入数十亿个节点)。另一个问题是,如果您拥有中等大小的元素,这些元素比一个单元格的大小大,并且通常跨越数十个单元格,那么您可能会浪费大量内存,将那些中等大小的元素插入比理想情况更多的单元格。同样,当您执行空间查询时,您可能不得不检查更多的单元格,有时甚至超出理想范围。

但是,对网格进行细化以使其在特定输入下尽可能最佳的唯一事情是cell size,这不会给您太多思考和摆弄,这就是为什么这是我的首选数据结构的原因解决空间索引问题,直到我找到不使用它的理由。它很容易实现,不需要您花任何时间来完成一个运行时输入。

您可以从普通的旧网格中获得很多收益,而我实际上已经击败了商业软件中使用的许多四叉树和kd树实现,方法是将它们替换为普通的旧网格(尽管它们不一定是最佳实现的网格) ,但是作者花费的时间比我花了20分钟才能完成一个网格要多得多)。这是我快速提出的小问题,它使用网格进行碰撞检测以解决其他问题(甚至还没有真正优化,只有几个小时的工作,而我不得不花费大部分时间来学习寻路的工作原理来回答问题这也是我第一次实现这种碰撞检测):

在此处输入图片说明

网格的另一个弱点(但它们是许多空间索引结构的一般弱点)是,如果您插入许多重合或重叠的元素(例如具有相同位置的许多点),它们将被插入到完全相同的单元格中),并在遍历该单元格时降低性能。同样,如果您插入大量远大于单元格大小元素,它们将需要插入大量单元格中,并使用大量内存,从而缩短了整个空间查询所需的时间。

但是,以上两个重合的,同时存在的和大量元素的问题实际上对于所有空间索引结构都是有问题的。普通的旧网格实际上处理这些病理情况比许多其他网格要好一些,因为它至少不想一遍又一遍地递归细分。

当您从网格开始并朝着四叉树或KD树之类的方向工作时,您要解决的主要问题是将元素插入到太多单元,单元太多以及/或必须使用这种密集表示形式检查太多的单元格。

但是如果您认为四叉树是对网格的优化对于特定的用例,那么仍然有必要考虑“最小像元大小”的思想,以限制四叉树节点的递归细分的深度。当您这样做时,最坏情况的四叉树场景仍将退化为叶子处的密集网格,效率仅低于网格,因为它需要对数时间才能从根到网格单元工作,而不是恒定时间。然而,考虑最小单元大小将避免无限循环/递归情况。对于大量元素,还存在一些替代变体,例如松散的四叉树,它们不一定会均匀拆分,并且可能有重叠的子节点AABB。BVH作为空间索引结构也很有趣,它不能平均细分其节点。对于针对树结构的重合元素,最主要的是只是对细分施加限制(或者如其他建议所建议,只是拒绝它们,或者找到一种方法来确定它们,当确定叶子何时形成叶子时,它们并没有对叶子中元素的唯一数量有所贡献)应该细分)。如果您预期输入包含许多重合元素,则Kd树也可能很有用,因为在确定节点是否应进行中位数拆分时,只需考虑一维。


作为四叉树的更新,有人问了一个广泛的问题(但我喜欢那些问题),关于如何使它们有效地用于碰撞检测,最后我对如何实现它们感到胆怯。它也应该回答您的问题:stackoverflow.com/questions/41946007/…–
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.