希尔伯特按分治算法排序?

Posted

技术标签:

【中文标题】希尔伯特按分治算法排序?【英文标题】:Hilbert sort by divide and conquer algorithm? 【发布时间】:2012-01-17 13:49:34 【问题描述】:

我正在尝试按希尔伯特顺序对 d 维数据向量进行排序,以便批量加载空间索引。

但是,我不想明确计算每个点的希尔伯特值,这尤其需要设置特定的精度。在高维数据中,这涉及到诸如32*d 位之类的精度,要有效地执行它会变得相当混乱。当数据分布不均匀时,其中一些计算是不必要的,并且需要对部分数据集进行额外的精度。

相反,我正在尝试使用分区方法。当你看二维一阶希尔伯特曲线时

1   4
|   |
2---3

我首先沿 x 轴拆分数据,这样第一部分(不一定包含一半对象!)将由 1 和 2(尚未排序)组成,第二部分将包含来自仅限 3 和 4。接下来,我将在 Y 轴上再次拆分每一半,但将顺序颠倒为 3-4。

所以本质上,我想执行一个分而治之的策略(与 QuickSort 密切相关 - 在均匀分布的数据上,这甚至应该是最佳的!),并且只根据需要计算希尔伯特索引的必要“位”。所以假设“1”中只有一个对象,那么就不需要计算它的完整表示;如果对象分布均匀,分区大小会迅速下降。

我知道转换为长、灰色编码、维度交错的常用教科书方法。这不是我要找的(有很多可用的例子)。我明确地想要一个懒惰的分而治之排序。另外,我需要的不仅仅是 2D。

有人知道以这种方式工作的文章或希尔伯特排序算法吗?或者一个关键的想法如何正确地“旋转”,为此选择哪种表示?特别是在更高维度中……在 2D 中它是微不足道的; 1 旋转 +y,+x,而 4 是 -y,-x(旋转和翻转)。但我猜,在更高维度上,这会变得更加棘手。

(结果当然应该与立即以足够大的精度按希尔伯特顺序对对象进行排序时相同;我只是想节省在不需要时计算完整表示的时间,并且必须管理它。许多人保留一个相当昂贵的哈希图“对象到希尔伯特数”。)

Peano 曲线和 Z 曲线应该可以使用类似的方法,并且可能更容易实现......我可能应该先尝试这些(Z 曲线已经在工作 - 它确实归结为类似于 QuickSort 的东西,使用适当的均值/网格值作为虚拟枢轴并在每次迭代的维度中循环)。

编辑:请参阅下文,了解我如何解决 Z 曲线和皮亚诺曲线。它也适用于二维希尔伯特曲线。但是我还没有希尔伯特曲线的旋转和反演。

【问题讨论】:

你能用 OctTree 代替吗? 不,八叉树不能很好地扩展到不平衡和高维数据。它们非常适合覆盖空间相当均匀的 3D 游戏。 这听起来像是动态希尔伯特 R-树,en.wikipedia.org/wiki/Hilbert_R-tree 是的,我打算通过将对象与它们的希尔伯特顺序(“希尔伯特打包”)进行预排序来使用它来批量加载 R-树。我不打算存储入口和/或出口点,所以它不会是一个完整的“动态希尔伯特 R-树”。 This link 可能是相关的。 【参考方案1】:

使用radix sort。将每个一维索引拆分为 d .. 32 部分,每个部分的大小为 1 .. 32/d 位。然后(从高位到低位)为每个索引块计算其希尔伯特值并将对象洗牌到适当的箱中。

这应该适用于均匀和不均匀分布的数据,无论是希尔伯特顺序还是 Z 顺序。并且不需要多精度计算。

关于将索引片段转换为希尔伯特顺序的一个细节:

首先提取必要的位, 然后交织来自所有维度的位, 然后将一维索引转换为逆格雷码。

如果索引以双精度存储:

如果索引可能为负数,请添加一些值以使所有内容都为正数,从而简化任务。 确定2的最小整数幂,大于所有索引,并将所有索引除以该值 将索引乘以 2^(当前排序步骤所需的位数)。 截断结果,将其转换为整数,并将其用于希尔伯特排序(交错并计算逆格雷码) 从索引中减去上一步截断的结果:index = index - i

来到您的基数排序变体,我建议使用两个大小为d 的二进制数组(一个主要用作堆栈,另一个用于反转索引位)扩展 zsort(以使 hilbertsort 脱离 zsort) ) 和旋转值(用于重新排列尺寸)。

如果栈顶值为1,则将pivotize(...升序)改为pivotize(...降序),然后在递归的第一部分,将这个栈顶值压入栈中,第二个- 推动这个值的倒数。此堆栈应在每次递归后恢复。它包含基数排序过程的最后d递归的“决策树”(逆格雷码)。

d 递归之后,这个“决策树”堆栈应该用于重新计算旋转值和反转数组。如何做到这一点的确切方法是不平凡的。它可以在以下链接中找到:hilbert.c 或 hilbert.c。

【讨论】:

回复:Edit3。似乎还没有奏效。根据我的草图,我实际上需要改变拆分顺序。在 2D 中,我的前两个拆分是 xy。在我上面的草图中,第三个拆分将是 x 立方体 23,而对于立方体 14 我将不得不拆分 y (分而治之) )。这对应于模式的旋转迭代。在 3D+ 中,它会变得更加复杂。 :-( 但是 +1 还是为了您的有用回复。 ringbuffer如何确定下一个分割轴? 在一个级别后分割的第一个轴因模式而异。在初始设置中(即拆分x,y后),14必须拆分y,x,而23必须拆分x,y。这对应于旋转使用的希尔伯特基本模式。 是的。该算法的某个地方存在缺陷。我需要思考。 唉,我为d>2 寻找工作算法的所有尝试都失败了。我想我应该删除这个答案。【参考方案2】:

您可以直接从 f(x)=y 计算希尔伯特曲线,而无需使用递归或 L 系统或分而治之。基本上它是格雷码或汉密尔顿路径遍历。您可以在 Nick 的空间索引希尔伯特曲线四叉树博客中找到很好的描述,或者从这本书黑客的喜悦中找到一个很好的描述。或者看看单调 n 元格雷码。我用 php 编写了一个包含摩尔曲线的实现。

【讨论】:

好吧,我不想想要计算希尔伯特曲线。我想根据对象的位置对对象进行排序,而不计算完整的希尔伯特曲线,而只是懒惰地计算我需要的那些分隔符。 @Anonymous:我不认为你可以计算半条曲线,但你可以使用表格,即预先计算的曲线。 好吧,我根本不看曲线。我只是在看单个位进行订购。 “视觉”的东西不是我感兴趣的。 @Anonymous:不幸的是,它被称为希尔伯特曲线,而你的建议是不同的。我知道你说的排序是什么意思。我自己用它。我写了一个带有摩尔曲线的 php 版本。希尔伯特曲线也不伤眼睛。【参考方案3】:

我已经回答了这个问题(和其他问题),但我的答案神秘地消失了。来自http://code.google.com/p/uzaygezen/source/browse/trunk/core/src/main/java/com/google/uzaygezen/core/CompactHilbertCurve.java 的紧凑希尔伯特索引实现(方法 index())已经允许将计算的希尔伯特索引位数限制为给定级别。来自上述方法的循环的每次迭代都会计算与空间维数相等的位数。您可以轻松地重构 for 循环以一次只计算一个级别(即,与空间的维数相等的位数),只需根据字典顺序比较两个数字的紧凑希尔伯特索引所需的深度。

【讨论】:

但代码仍然是逐级的,而不是逐位的。对于高维设置,比如 128 维,这似乎比必要的工作要多一些,因为比 128 维少得多实际上已经足以识别我的所有数据(至少除非非常密集)。

以上是关于希尔伯特按分治算法排序?的主要内容,如果未能解决你的问题,请参考以下文章

八大内部排序算法之希尔堆排序插入排序算法

算法设计与分析分治法--快速排序的递归和非递归实现

排序算法总结

从减治法到插入排序再到希尔排序

各种排序算法总结篇(高速/堆/希尔/归并)

JavaScript 数据结构与算法之美 - 归并排序快速排序希尔排序堆排序