Как я могу сгруппировать массив прямоугольников в" острова " связанных областей?
проблема
у меня есть массив java.awt.Rectangle
s. Для тех, кто не знаком с этим классом, важной частью информации является то, что они предоставляют
8 ответов
что вы хотите найти подключенные компоненты. То есть представьте себе граф, вершины которого соответствуют прямоугольникам, и где есть ребро между двумя вершинами, если соответствующие прямоугольники пересекаются. Тогда вы хотите найти и метка связные компоненты этого графа.
просто нахождение ребер (определение для каждой пары прямоугольников, пересекаются ли они) принимает O (n2) время, после которого вы можете использовать либо глубину или поиск в ширину чтобы найти все компоненты в дополнительное время O (E), где E 2.
в псевдокоде (простое упражнение для перевода его на 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(n2). На самом деле, этот алгоритм предназначен для общих графов, но потому что ваш график скорее special-происходит от прямоугольников - вы можете сделать еще лучше: на самом деле можно решить проблему в o(n log n) всего времени, используя сегмент деревья.
хорошо, думаю, я понял. Этот алгоритм довольно неэффективен, O (n^3) по расчету ВИЧа, но он, похоже, работает.
Я Set
вместо List
на getIntersections()
чтобы не считать дважды один и тот же прямоугольник (хотя я не думаю, что это действительно необходимо). Я думаю, ваш окончательный результат может быть даже Set<Set<Rectangle>>
но алгоритм должен быть примерно такой же. Я также использовал List
s везде вместо массивов, потому что я думаю, что массивы уродливы, но это достаточно легко конвертируйте обратно, если нужно. Набор 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;
}
Я не на своем java foo, но я думаю, проблема в том, что вы удаляете элементы из своего списка во время итерации списка. В зависимости от реализации типа контейнера, это может иметь большие проблемы. Кто-то с большим знанием Java может подтвердить или опровергнуть это.
этой и кажется, подтверждает мои подозрения.
после гугления немного кажется, что итераторы 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).
(это слишком долго для комментария)
быстрый рисунок делает не показать, что A пересекает B: A имеет высоту 4, в то время как B начинается с позиции Y 5, как они могут пересекаться!?
вы можете проверить это со следующим, что печатает "false":
System.out.println( new Rectangle(0, 0, 2, 4).intersects( new Rectangle(1, 5, 4, 2) ) );
тогда ваша подпись метода является неполной, так же как и ваш пример кода.
Если вы немного проясните свою проблему и дадите рабочий, правильный пример, то у меня есть очень хорошее решение для вас.
Если вы хотите алгоритм O(N log n), один был показан Imai и Asano в поиск связанных компонентов и максимальной клики графа пересечения прямоугольников в плоскости.
Примечание: я все еще работаю над собственным алгоритмом развертки плоскости, чтобы найти множество в O (N log n) времени.
Это решение, которое я выбрал в конце. Кто-нибудь может догадаться о его эффективности?
пакета 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;
}
}
вы не можете удалить объект из списка итерации, объект итератора или нет, вам нужно будет найти другой способ
кроме того, поскольку у вас есть только прямоугольники могут создать очень эффективным!--8-->алгоритм развертки линии.
Я ожидал бы, что лучший алгоритм займет по крайней мере O( n^2 )
время, потому что дал n
прямоугольники есть O( n^2 )
возможные пересечения и любой алгоритм для вычисления того, что вы хотите, должны были бы рассмотреть все пересечения в какой-то момент.