Как я могу сгруппировать массив прямоугольников в “Острова” связанных регионов?

Проблема

У меня есть массив java.awt.Rectangles. Для тех, кто не знаком с этим классом, важная информация - то, что они обеспечивают .intersects(Rectangle b) функция.

Я хотел бы записать функцию, которая берет этот массив Rectangles, и разбивает его в группы связанных прямоугольников.

Позволяет говорят, например, что это мои прямоугольники (конструктор берет аргументы 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);
    //...
}

Кто-либо может пролить некоторый свет на проблему?

13
задан Eric 12 February 2010 в 21:27
поделиться

8 ответов

Вот решение, которое я выбрал в итоге. Может ли кто-нибудь предположить его эффективность?

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;
    }
}
1
ответ дан 2 December 2019 в 01:21
поделиться

Хорошо, думаю, я понял. Этот алгоритм довольно неэффективен, O (n ^ 3) по расчетам, но, похоже, он работает.

Я использовал Set вместо List в getIntersections () , чтобы не подсчитывать один и тот же прямоугольник дважды (хотя я не думаю, что это действительно необходимо ). Я предполагаю, что ваш окончательный результат может быть даже Set > , но алгоритм должен быть примерно таким же. Я также везде использовал List вместо массивов, потому что я считаю массивы некрасивыми, но при необходимости их достаточно легко преобразовать обратно. Набор newRectanglesToBeAdded позволяет нам решить, нужно ли нам продолжать цикл или нет, а также удерживает нас от добавления в список, пока мы повторяем его (что так же плохо, как пытаться удалить что-то из list, пока мы повторяем его). Я не думаю, что это самое элегантное решение, но похоже, что оно работает (по крайней мере, для предоставленных вами тестовых данных).

  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;
  }
2
ответ дан 2 December 2019 в 01:21
поделиться

Вы не можете удалить объект из списка, через который выполняется итерация, вне зависимости от объекта итератора, вам нужно будет найти другой способ

0
ответ дан 2 December 2019 в 01:21
поделиться

Основные параметры:

  • Если в первую очередь используется интеграция Visual Studio, достаточно просто пометить эти файлы как «Исключить из системы управления версиями» в решениях/проектах.
  • Администратор может заблокировать файл. TFS поддерживает два типа блокировок в зависимости от того, где в процессе вы хотите, чтобы разработчики получили шлепок с предупреждением: http://blogs.msdn.com/phkelley/archive/2008/11/12/everything-you-ever-wanted-to-know-about-locks.aspx

Есть несколько других способов, но я думаю, что это четкие фавориты.

-121--4998160-

Поле делегата по-прежнему является полем, а не методом - оно само по себе не принимает параметры. Параметры относятся к типу делегата , а не к полю делегата . При необходимости можно написать комментарии для параметров при делегировании типов.

/// <summary>
/// Tests something.
/// </summary>
/// <param name="test">Something that's going to be tested.</param>
delegate void Test(int test);

Func < последовательность, последовательность, последовательность > является общим делегатом для функций с тремя параметрами. Для конкретных целей всегда можно объявить собственный тип делегата, более конкретно представляющий абстрактный метод, и добавить комментарии к его параметрам.

-121--3665284-

Подключенные компоненты .

Кроме того, поскольку у вас есть только прямоугольники, вы можете разработать очень эффективный алгоритм линии сдвига .

Я бы ожидал, что лучший алгоритм займет по крайней мере O (n ^ 2) времени, потому что учитывая n прямоугольников есть O (n ^ 2) возможных пересечений, и любой алгоритм для вычисления того, что вы хотите, должен будет рассмотреть все пересечения в какой-то пункт.

0
ответ дан 2 December 2019 в 01:21
поделиться

Если вам нужен алгоритм O (n log n), один из них был показан Имаи и Асано в Поиск связанных компонент и максимальной клики графа пересечений прямоугольников на плоскости .

Примечание: я все еще работаю над своим собственным алгоритмом развертки плоскости, чтобы найти набор за время O (n log n).

1
ответ дан 2 December 2019 в 01:21
поделиться

(это слишком долго для комментария)

Быстрый рисунок НЕ показывает, что A пересекает B: A имеет высота 4, в то время как B начинаются с позиции Y, равной 5, как они могут пересекаться!?

Вы можете проверить это, выполнив следующую команду, которая выводит «false»:

System.out.println( new Rectangle(0, 0, 2, 4).intersects( new Rectangle(1, 5, 4, 2) ) );

Тогда подпись вашего метода неполна, как и ваш пример кода .

Если вы немного проясните свою проблему и приведете рабочий, правильный пример, то у меня есть для вас очень хорошее решение.

1
ответ дан 2 December 2019 в 01:21
поделиться

Вам нужно найти связанных компонентов . То есть представьте граф, вершины которого соответствуют прямоугольникам, и где есть ребро между двумя вершинами, если соответствующие прямоугольники пересекаются. Затем вы хотите найти и разметить компоненты связности этого графа.

Простое нахождение ребер (определение для каждой пары прямоугольников пересечения) занимает O (n 2 ) времени, после чего вы можете использовать либо поиск в глубину или поиск в ширину , чтобы найти все компоненты за дополнительное время 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 (n 2 ). Фактически, этот алгоритм предназначен для общих графиков, но поскольку ваш график довольно особенный - он состоит из прямоугольников - вы можете добиться еще большего: на самом деле можно решить проблему за O (n log n) всего времени, используя деревья сегментов .

3
ответ дан 2 December 2019 в 01:21
поделиться

Я не знаю java foo, но думаю, проблема в том, что вы удаляете элементы из своего списка во время итерации списка. В зависимости от реализации типа контейнера могут возникнуть большие проблемы. Кто-то с большим знанием Java может подтвердить или опровергнуть это.

Этот вопрос SO , кажется, подтверждает мои подозрения.

После небольшого поиска в Google кажется, что итераторы Java поддерживают метод удаления, поэтому вместо

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 December 2019 в 01:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: