我正在从事与计算几何有关的个人项目。标题中的问题是我正在尝试的一个小的子问题的抽象,但是努力解决,有效地解决。希望它足够通用,可能不仅仅是我的使用!
想象一下,我们在平面上有一组S矩形,所有矩形的边都与坐标轴平行(没有旋转)。对于我的问题,我们假设矩形交叉点非常常见。但它们也非常好:如果两个矩形相交,我们可以假设其中一个总是完全包含另一个。所以没有"部分"重叠。
我希望以下列方式存储这些矩形:
插图提供了后者的动机。我们总是希望找到包含查询点的最深嵌套矩形,因此它始终是最小区域之一。
所以我知道R-Trees和Quad-Trees都经常用于空间索引问题,实际上两者在某些情况下都可以正常工作。 R-Trees的问题在于它们在最坏的情况下会降低到线性性能。
我考虑过基于嵌套构建一组平衡二叉树。节点r的左子树包含矩形r内的所有矩形。右子树包含r所在的所有矩形。图示的例子将有三棵树。
但如果没有嵌套的矩形怎么办?然后你需要1个元素的O(n)树,并且我们再次拥有的东西与通过盒子的线性扫描一样差。
我怎么能以最坏情况下渐近子线性时间的方式解决这个问题?即使这意味着在最佳情况或存储要求中牺牲一些性能。 (我认为对于这样的问题,可能需要维护两个数据结构并且很酷)
我确信允许矩形交叉的非常具体的方式应该有助于解决这个问题。事实上,它对我来说似乎是对数性能的候选者,但我只是没有到达任何地方。
提前感谢您的任何想法!
答案 0 :(得分:4)
我建议每个嵌套级别存储矩形,并处理每个级别的矩形查找。一旦找到了该点所在的顶级矩形,您就可以查看该矩形内的第二级矩形,使用相同的方法找到该点所在的矩形,然后查看第三个等级,等等。
为了避免O(n)找到矩形的最坏情况,你可以使用一种三元空间树,在那里你重复在空间上绘制一条垂直线并将矩形分成三组:左(蓝色),与(红色)相交的那些,以及与线的右(绿色)相交的那些。对于交叉矩形组(或者一旦垂直线与大多数或所有矩形相交),您切换到水平线并将矩形划分为线上方,相交和下方的组。
然后,您将反复检查该点是在线的左侧/右侧还是上方/下方,然后继续检查同一侧的矩形和线相交的矩形。
在该示例中,实际上只需要检查四个矩形以找到包含该点的矩形。
如果我们在示例中对矩形使用以下编号:
然后三元空间树将是这样的:答案 1 :(得分:1)
您可以沿着矩形的边缘将区域从xMin划分为xMax,将yMin划分为yMax。这最多给出(2n-1)^ 2个字段。每个字段要么完全为空,要么由可见(部分)单个矩形占据。现在,您可以轻松创建一个带有指向顶部矩形的链接的树结构(例如,计算x和y方向上的分区数,其中中间有更多的分隔并创建节点...递归执行)。因此查找将采用亚线性的O(log n ^ 2)。数据结构占用O(n ^ 2)空间。
在复杂性方面,这是一个更好的实现,因为索引的搜索可以分开,顶部的矩形搜索只有O(log n),无论矩形的配置如何和公平易于实施:
private int[] x, y;
private Rectangle[][] r;
public RectangleFinder(Rectangle[] rectangles) {
Set<Integer> xPartition = new HashSet<>(), yPartition = new HashSet<>();
for (int i = 0; i < rectangles.length; i++) {
xPartition.add(rectangles[i].getX());
yPartition.add(rectangles[i].getY());
xPartition.add(rectangles[i].getX() + rectangles[i].getWidth());
yPartition.add(rectangles[i].getY() + rectangles[i].getHeight());
}
x = new int[xPartition.size()];
y = new int[yPartition.size()];
r = new Rectangle[x.length][y.length];
int c = 0;
for (Iterator<Integer> itr = xPartition.iterator(); itr.hasNext();)
x[c++] = itr.next();
c = 0;
for (Iterator<Integer> itr = yPartition.iterator(); itr.hasNext();)
y[c++] = itr.next();
Arrays.sort(x);
Arrays.sort(y);
for (int i = 0; i < x.length; i++)
for (int j = 0; j < y.length; j++)
r[i][j] = rectangleOnTop(x[i], y[j]);
}
public Rectangle find(int x, int y) {
return r[getIndex(x, this.x, 0, this.x.length)][getIndex(y, this.y, 0, this.y.length)];
}
private int getIndex(int n, int[] arr, int start, int len) {
if (len <= 1)
return start;
int mid = start + len / 2;
if (n < arr[mid])
return getIndex(n, arr, start, len / 2);
else
return getIndex(n, arr, mid, len - len / 2);
}
答案 2 :(得分:1)
几乎任何索引都可以降级到最坏情况O(n)。
问题是,您是否会有这样的有害数据,以及您是否针对最坏情况或实际数据进行优化。
考虑n个相同大小,重叠的矩形,以及交叉点中的一个点......在这里你将没有太多机会进行优化。
R树是一个非常好的选择。您可以执行优先搜索,并选择较小的矩形。
但是您的草图表明您的矩形通常可以嵌套,而不是重叠。标准的R-tree不能很好地处理这个问题。相反,您可能需要将R树修改为使用这个结构,并且只将嵌套的矩形存储为父级的一部分。
答案 3 :(得分:1)
PH-Tree怎么样? PH-Tree基本上是四叉树形状,如四叉树,但具有一些独特的属性,可能是您的理想选择,例如非常有效的更新和小矩形的高可能性。
基础:
存储矩形:PH-Tree只能存储数据向量,即。点。为了存储(轴对齐)矩形,默认情况下它采用“左下”和“右上角”,但这些是单向量。例如,2D矩形(2,2) - (4,5)被存储为4维向量(2,2,4,5)。这可能并不明显,但是这种表示仍然允许有效的查询,例如窗口查询和最近邻查询,查看一些结果here以及更多解释here。
树不能直接存储两次相同的矩形。相反,您可以将计数器与每个“键”相关联。对于具有'n'个相同矩形的特殊情况,这实际上具有以下优点:结果树将仅包含一个键,因此可以在几乎恒定的时间内确定与最小矩形的重叠。
查询性能:从性能结果可以看出,PH-Tree(取决于数据集)最快,小查询窗口返回的结果很少(here,图16)。我不确定性能优势是否与小查询窗口大小或小结果大小相关。但如果它连接到第一个,那么你的查询应该非常快,因为基本上你的查询窗口就是一个点。
优化小矩形大小:由于将矩形编码为单个向量,最小的矩形很可能(保证??)位于同一个叶节点中,该节点也包含您的搜索点。 通常,查询以z顺序遍历,因此要利用小矩形的局部性,您需要编写特殊查询。这应该不难,我想我可以简单地使用PH-Tree k-nearest-neighbor实现并提供自定义距离函数。当前kNN开始于使用搜索点定位节点,然后扩展搜索区域,直到找到所有最近邻居。我相信使用自定义距离函数应该足够了,但你可能需要做一些研究来证明它。
上面的链接中提供了PH-Tree的完整代码(Java)。为了进行比较,您可能想要查看我的其他索引实现here(R * Tree,quadtrees,STR-Tree)。