Как я могу сгруппировать массив прямоугольников в" острова " связанных областей?

проблема

у меня есть массив java.awt.Rectangles. Для тех, кто не знаком с этим классом, важной частью информации является то, что они предоставляют

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>> но алгоритм должен быть примерно такой же. Я также использовал Lists везде вместо массивов, потому что я думаю, что массивы уродливы, но это достаточно легко конвертируйте обратно, если нужно. Набор 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 ) возможные пересечения и любой алгоритм для вычисления того, что вы хотите, должны были бы рассмотреть все пересечения в какой-то момент.