Алгоритм: объединить перекрывающиеся сегменты

у меня есть следующий ADT (не отсортирован):List<Segment>

//direction is from 0 to 2pi
class Segment {
    int start;
    int end;
}

Они представляют, например, эту ситуацию: enter image description here

Как сделать фазу слияния (зеленая стрелка в Примере)? Очевидно, мне нужно перебрать список и каждый сегмент сравнить со всеми другими сегментами, и для каждой пары, если возможно, сделать простое слияние (это легко). Но затем на второй итерации мне нужно как-то вернуться к началу списка и начать все сначала и т. д... Поэтому я пытаюсь найти, как этот алгоритм будет сходиться.

EDIT: сегменты могут быть круговыми - от 1.75 pi до 0.5 pi и т. д...

4 ответов


сортировка сегментов по времени начала.

создайте стек, в котором будут храниться объединенные интервалы.

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

если время начала больше времени окончания элемента в верхней части стека, добавьте интервал в стек.

Если время начала меньше времени окончания элемента в в верхней части стека обновите время окончания элемента в верхней части стека, чтобы оно соответствовало времени окончания нового элемента.

при обработке всего массива результирующий стек должен содержать объединенные интервалы.

реализация на java:

/**
 * Definition for an interval.
 * public class Interval {
 *     int start;
 *     int end;
 *     Interval() { start = 0; end = 0; }
 *     Interval(int s, int e) { start = s; end = e; }
 * }
 */
public class Solution {
    public List<Interval> merge(List<Interval> intervals) {
        Deque<Interval> stack = new ArrayDeque<Interval>();

        Collections.sort(intervals, new Comparator<Interval>() {
                public int compare(Interval p1, Interval p2) {
                    return Integer.compare(p1.start, p2.start);
                }
            }
        );

        if (intervals.size() < 1) {
            return intervals;
        }
        stack.push(intervals.get(0));

        for (int j = 1; j < intervals.size(); j++) {
            Interval i = intervals.get(j);
            Interval top  = stack.peek();
            if (top.end < i. start) {
                stack.push(i);
            }
            else if (top.end < i.end) {
                top.end = i.end;
            }
        }
        return new ArrayList<Interval>(stack);

    }
}

сортировка сегментов по начальной точке.

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

если текущий сегмент полностью включен в предыдущий сегмент, просто удалите / проигнорируйте его.

это O(n log n) из-за сортировки, и вам не нужно сравнивать каждый сегмент со всеми другими сегментами.

будьте осторожны, как вы делаете удаление или игнорирование. Это должно быть O(1) операции. Например, сохраните переменную, которая всегда хранит предыдущий не удаленный сегмент, и, возможно, флаг для удаленных сегментов, чтобы вы знали, какие из них собирать в конце. .remove() операции над коллекциями можете быть O(n), и вы хотите избежать этого.


Поместите все конечные точки в один массив и назначьте им полярность (+ затем -). Затем отсортируйте список.

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

0+ 0.75- 0.5+ 1- 1.25+ 2-

затем сортируют,

0+ 0.5+ 0.75- 1- 1.25+ 2-

дает количество (инициализируется в 0)

1 2 1 0 1 0

следовательно, границы интервалов (на переходах 0 to >0 или >0 to 0)

0 1 1.25 2

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

С start и end значения отдельно, на месте (не перемещать Segments в целом); таким образом, полярности остаются неявными.

затем пересечь список как слияние двух отсортированных списков (с использованием двух независимых индексов) и сохранить счетчик перекрытий. Можно изменить границы на месте, как результат слияния не имеет больше интервалов.

начиная от

[0 0.75][0.5 1][1.25 2]

оба списка случайно уже отсортированный.

0 0.5 1.25 (+)
0.75 1 2   (-)

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

+ + - - + -

и конечный результат получается путем сдвига значений

[0 1][1.25 2][x x]

в случае связей на границах, лучше обрабатывать + и - для того, чтобы избежать испуская две равные границы.


Я добавлю к другим ответам подход для циркулярности, т. е. что вы должны делать, если у вас есть некоторые сегменты, зацикливающиеся на 2pi.

есть два способа справиться с этим. Один из них состоит в том, чтобы просто разделить каждый такой сегмент на два: один идет откуда угодно до 2pi, другой идет от нуля до где угодно. После этого решите задачу так, как будто она не круговая, а затем, если у вас есть сегмент, начинающийся с нуля, и сегмент, заканчивающийся на 2pi, то просто объедините их.

второй подход специально для ответа Ив Дауст по. Там все, что вам нужно, это знать, сколько сегментов покрывают нулевую точку (вы можете легко вычислить это); после этого вы инициализируете "подсчеты" не с нулем, а с этим количеством охватывающих сегментов.