我有一个java.awt.Rectangle
的数组。对于那些不熟悉这门课程的人来说,重要的信息是它们提供.intersects(Rectangle b)
功能。
我想编写一个函数,它接受Rectangle
的这个数组,并将其分解为连接的矩形组。
让我们举例说,这些是我的矩形(构造函数接受参数x
,y
,width
,height
):
Rectangle[] rects = new Rectangle[]
{
new Rectangle(0, 0, 4, 2), //A
new Rectangle(1, 1, 2, 4), //B
new Rectangle(0, 4, 8, 2), //C
new Rectangle(6, 0, 2, 2) //D
}
快速绘图显示A相交B和B相交C.D不相交。一个繁琐的ascii艺术作品也起到了作用:
┌───────┐ ╔═══╗
│A╔═══╗ │ ║ D ║
└─╫───╫─┘ ╚═══╝
║ B ║
┌─╫───╫─────────┐
│ ╚═══╝ C │
└───────────────┘
因此,我的函数输出应为:
new Rectangle[][]{
new Rectangle[] {A,B,C},
new Rectangle[] {D}
}
这是我尝试解决问题的方法:
public List<Rectangle> getIntersections(ArrayList<Rectangle> list, Rectangle r)
{
List<Rectangle> intersections = new ArrayList<Rectangle>();
for(Rectangle rect : list)
{
if(r.intersects(rect))
{
list.remove(rect);
intersections.add(rect);
intersections.addAll(getIntersections(list, rect));
}
}
return intersections;
}
public List<List<Rectangle>> mergeIntersectingRects(Rectangle... rectArray)
{
List<Rectangle> allRects = new ArrayList<Rectangle>(rectArray);
List<List<Rectangle>> groups = new ArrayList<ArrayList<Rectangle>>();
for(Rectangle rect : allRects)
{
allRects.remove(rect);
ArrayList<Rectangle> group = getIntersections(allRects, rect);
group.add(rect);
groups.add(group);
}
return groups;
}
不幸的是,这里似乎有一个无限的递归循环。我没有受过教育的猜测是java不喜欢我这样做:
for(Rectangle rect : allRects)
{
allRects.remove(rect);
//...
}
有人能解释一下这个问题吗?
答案 0 :(得分:3)
你想要的是找到connected components。也就是说,想象一个图形,其顶点对应于矩形,并且如果相应的矩形相交,则在两个顶点之间存在边缘。然后,您要查找并label此图表的已连接组件。
找到边缘(确定每对矩形是否相交)需要O(n 2 )时间,之后你可以使用depth-first search或{{3在额外的O(E)时间内找到所有组件,其中E
在伪代码(将其转换为Java的简单练习)中,它可能看起来像这样:
# r is the list of rectangles
n = length of r (number of rectangles)
#Determine "neighbors" of each vertex
neighbors = (array of n lists, initially empty)
for i in 1 to n:
for j in 1 to n:
if i!=j and r[i].intersects(r[j]):
neighbors[i].append(j)
#Now find the connected components
components = (empty list of lists)
done = (array of n "False"s)
for i in 1 to n:
if done[i]: continue
curComponent = (empty list)
queue = (list containing just i)
while queue not empty:
r = pop(head of queue)
for s in neighbors[r]:
if not done[s]:
done[s] = True
queue.push(s)
curComponent.push(s)
#Everything connected to i has been found
components.push(curComponent)
return components
我正在预先计算邻居并使用“完成”标签来保存O(n)因子并使整个事物O(n 2 )。事实上,这个算法适用于一般图形,但是因为你的图形非常特殊 - 来自矩形 - 你可以做得更好:实际上可以使用{{3来解决O(n log n)时间总计中的问题。 }}
答案 1 :(得分:2)
我不喜欢我的java foo,但我想问题是你在迭代列表时从列表中删除项目。根据容器类型的实现,这可能会有很大的问题。拥有更多Java知识的人可能能够确认或否认这一点。
这SO Question似乎证实了我的怀疑。
在google搜索之后,似乎java迭代器支持remove方法,因此不是
allRects.remove(rect);
你应该使用迭代器,然后使用
rect_i.remove();
和
相同list.remove(rect);
虽然我认为这仍然会让您遇到麻烦,因为您在调用堆栈的较低级别修改了相同的列表。
我的版本:
ArrayList<Rectangle> rects = new ArrayList<Rectangle>(rectArray);
ArrayList<ArrayList<Rectangle>> groups = new ArrayList<ArrayList<Rectangle>>();
while (!rects.isEmpty)
{
ArrayList<Rectangle> group = new ArrayList<Rectangle>();
ArrayList<Rectangle> queue = new ArrayList<Rectangle>();
queue.add(rects.remove(0));
while (!queue.isEmpty)
{
rect_0 = queue.remove(0);
rect_i = rects.iterator();
while (rect_i.hasNext())
{
Rectangle rect_1 = rect_i.next();
if (rect_0.intersects(rect_1))
{
queue.add(rect_1);
rect_i.remove();
}
}
group.add(rect_0);
}
groups.add(group);
}
注意:我认为代码现在是正确的,但我只是从参考文档中编写了这个,而且我不是Java编码器,所以你可能需要调整。
顺便说一句,如果您需要检查一小部分矩形,这种类型的天真算法很好,但如果您想为非常大的列表执行此操作,那么您将需要使用更高效的算法。这种朴素算法是O(n ^ 2),这是一种更智能的算法,首先按字典顺序对所有矩形角进行排序,然后执行平面扫描,并对扫描线进行距离交叉检查将产生相对简单的O(n log n)算法。
答案 2 :(得分:2)
好吧,我想我明白了。这个算法效率很低,通过计算得到了O(n ^ 3),但似乎确实有效。
我在Set
中使用了List
而不是getIntersections()
来防止对同一个矩形进行两次计数(尽管我认为这实际上并不是必需的)。我猜你的最终结果甚至可能是Set<Set<Rectangle>>
,但算法应该大致相同。我还在任何地方使用List
而不是数组,因为我认为数组是丑陋的,但如果你需要,它很容易转换回来。集合newRectanglesToBeAdded
让我们决定是否需要保持循环,并且在我们迭代它时阻止我们添加到列表中(这与尝试从列表中删除内容一样糟糕)我们正在迭代它。我不认为这是最优雅的解决方案,但似乎有效(至少对于您提供的测试数据而言)。
public static Set<Rectangle> getIntersections(List<Rectangle> list,
Rectangle r) {
Set<Rectangle> intersections = new HashSet<Rectangle>();
intersections.add(r);
Set<Rectangle> newIntersectionsToBeAdded = new HashSet<Rectangle>();
do {
newIntersectionsToBeAdded.clear();
for (Rectangle r1 : list) {
for (Rectangle r2 : intersections) {
if (!intersections.contains(r1) && r2.intersects(r1)) {
newIntersectionsToBeAdded.add(r1);
}
}
}
intersections.addAll(newIntersectionsToBeAdded);
} while (!newIntersectionsToBeAdded.isEmpty());
return intersections;
}
public static List<Set<Rectangle>> mergeIntersectingRects(List<Rectangle> allRects) {
List<Set<Rectangle>> grouped = new ArrayList<Set<Rectangle>>();
while (!allRects.isEmpty()) {
Set<Rectangle> intersections = getIntersections(allRects, allRects.get(0));
grouped.add(intersections);
allRects.removeAll(intersections);
}
return grouped;
}
答案 3 :(得分:1)
(评论太长了)
快速绘图 NOT 显示A与B相交:A高度为4而B从Y位置开始为5,它们如何交叉!?
您可以使用打印出'false'的以下内容对其进行验证:
System.out.println( new Rectangle(0, 0, 2, 4).intersects( new Rectangle(1, 5, 4, 2) ) );
然后您的方法签名不完整,所以是您的代码示例。
如果你澄清一下你的问题并给出一个正常的,正确的例子,那么我有一个非常好的解决方案。
答案 4 :(得分:1)
如果你想要一个O(n log n)算法,Imai和Asano在Finding the connected components and a maximum clique of an intersection graph of rectangles in the plane中展示了一个算法。
注意:我仍在使用自己的平面扫描算法在O(n log n)时间内找到该集合。
答案 5 :(得分:1)
这是我最终采用的解决方案。任何人都可以猜测它的效率吗?
package java.util;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
public class RectGroup extends ArrayList<Rectangle> implements List<Rectangle>
{
public RectGroup(Rectangle... rects)
{
super(rects);
}
public RectGroup()
{
super();
}
public boolean intersects(Rectangle rect)
{
for(Rectangle r : this)
if(rect.intersects(r))
return true;
return false;
}
public List<RectGroup> getDistinctGroups()
{
List<RectGroup> groups = new ArrayList<RectGroup>();
// Create a list of groups to hold grouped rectangles.
for(Rectangle newRect : this)
{
List<RectGroup> newGroupings = new ArrayList<RectGroup>();
// Create a list of groups that the current rectangle intersects.
for(RectGroup group : groups)
if(group.intersects(newRect))
newGroupings.add(group);
// Find all intersecting groups
RectGroup newGroup = new RectGroup(newRect);
// Create a new group
for(List<Rectangle> oldGroup : newGroupings)
{
groups.remove(oldGroup);
newGroup.addAll(oldGroup);
}
// And merge all the intersecting groups into it
groups.add(newGroup);
// Add it to the original list of groups
}
return groups;
}
}
答案 6 :(得分:0)
你无法从迭代通过迭代器对象的列表中删除对象,你需要找到另一种方式
答案 7 :(得分:0)
或者,因为您只有矩形,所以您可以设计出非常高效的sweep line algorithm。
我希望最好的算法至少花费O( n^2 )
时间,因为给定的n
矩形有O( n^2 )
个可能的交叉点,任何计算你想要的算法都需要考虑所有在某些时候交叉点。