Алгоритм Балабана — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Split)
(Search In Strip)
Строка 113: Строка 113:
 
Учитывая [[#lemma1|лемму]], заключаем, что функция <tex>SearchInStrip_{a, b}(L, R)</tex> работает за <tex>O(|L| + |Int_{a, b}(L)|)</tex>.
 
Учитывая [[#lemma1|лемму]], заключаем, что функция <tex>SearchInStrip_{a, b}(L, R)</tex> работает за <tex>O(|L| + |Int_{a, b}(L)|)</tex>.
  
Предположим, что все отрезки лежат в полосе <tex>\langle a, b \rangle</tex>. Таким образом в самом начале у нас есть пара <tex>(S, \langle a, b, \rangle)</tex>.
+
Предположим, что все отрезки лежат в полосе <tex>\langle a, b\rangle</tex>. Таким образом в самом начале у нас есть пара <tex>(S, \langle a, b\rangle)</tex>.
 
Что же дальше происходит: множество <tex>S</tex> ''распадается'' в подмножества <tex>Q</tex> и <tex>S'</tex>, после чего лестница <tex>D = (Q, \langle a, b \rangle)</tex> становится полностью соотносимой множеству <tex>S'</tex>. Необходимо найти пересечения отрезков из <tex>D</tex> и <tex>S'</tex>, затем, все пересечения в <tex>S'</tex>. Чтобы найти пересечения отрезков в <tex>S'</tex>, мы ''режем'' полосу <tex>\langle a, b \rangle</tex> и множество <tex>S'</tex> по вертикале <tex>x = c</tex> на полосы <tex>\langle a, c \rangle</tex>, <tex>\langle c, b \rangle</tex> и множества <tex>S'_{ls}</tex>, <tex>S'_{rs}</tex> соответственно, где c является медианой вершин отрезков, между <tex>a</tex> и <tex>b</tex>. Затем мы рекурсивно вызываем функцию к парам <tex>(S'_{ls}, \langle a, c \rangle)</tex> и <tex>(S'_{rs}, \langle c, b \rangle)</tex>. Ключевым является тот факт, что согласно [[#lemma1|лемме]] <tex>|S'| \le Ends_{a, b}(S') + |Int(D, S')|</tex>, таким образом, число дополнительных отрезков, появляющихся после ''разрезаний'' пропорционально числу найденных пересечений.
 
Что же дальше происходит: множество <tex>S</tex> ''распадается'' в подмножества <tex>Q</tex> и <tex>S'</tex>, после чего лестница <tex>D = (Q, \langle a, b \rangle)</tex> становится полностью соотносимой множеству <tex>S'</tex>. Необходимо найти пересечения отрезков из <tex>D</tex> и <tex>S'</tex>, затем, все пересечения в <tex>S'</tex>. Чтобы найти пересечения отрезков в <tex>S'</tex>, мы ''режем'' полосу <tex>\langle a, b \rangle</tex> и множество <tex>S'</tex> по вертикале <tex>x = c</tex> на полосы <tex>\langle a, c \rangle</tex>, <tex>\langle c, b \rangle</tex> и множества <tex>S'_{ls}</tex>, <tex>S'_{rs}</tex> соответственно, где c является медианой вершин отрезков, между <tex>a</tex> и <tex>b</tex>. Затем мы рекурсивно вызываем функцию к парам <tex>(S'_{ls}, \langle a, c \rangle)</tex> и <tex>(S'_{rs}, \langle c, b \rangle)</tex>. Ключевым является тот факт, что согласно [[#lemma1|лемме]] <tex>|S'| \le Ends_{a, b}(S') + |Int(D, S')|</tex>, таким образом, число дополнительных отрезков, появляющихся после ''разрезаний'' пропорционально числу найденных пересечений.
  

Версия 16:21, 15 ноября 2013

Эта статья находится в разработке!

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

Введение

Решение задачи по поиску множества пересечений отрезков является одной из главных задач вычислительной геометрии. Тривиальный детерминированный алгоритм имеет временную сложность O(n2), и его суть заключается в проверке попарного пересечения отрезков. Сложнее, но эффективнее алгоритм Бентли-Оттмана [1] с оценкой сложности O((n+k) log(n)+k), в основе которого лежит метод заметающей прямой. Алгоритм, предложенный Чазелле и Едельсбруннером [2], имеет лучшую оценку O(n log(n)+k), но в отличие от предыдущих методов требует квадратичной памяти. Оптимальный детерминированный алгоритм был предложен Балабаном [3] с временной оценкой сложности O(n log(n)+k) и O(n) памяти, где К - число пересекающихся отрезков. При количестве отрезков от 2000, и большому количеству пересечений целесообразно использовать алгоритм Балабана. Однако в результате громоздкости и высокой сложности реализации алгоритма, в большинстве практических задач используется алгоритм заметающей прямой Бентли-Оттмана.

Основные понятия

Введем некоторые обозначения. Пусть Int(S) - множество всех точек пересечения отрезков из множества S, а K=|Int(S)| - количество таких пересечений ;
Через a,b обозначим вертикальную полосу, которая ограничена прямыми x=a и x=b, а через s — отрезок с вершинами в точках с абсциссами l и r.
Рассмотрим взаимное расположение вертикальной полосы a,b и отрезка s.


Определение:
Будем говорить, что отрезок s, с вершинами в точках с абсциссами l и r :

- содержит(span) полосу a,b, если labr;
- внутренний(inner) для полосы a,b, если a<l<r<b;

- пересекает(cross) полосу a,b в других случаях.


Определение:
Два отрезка s1 и s2 называются пересекающимися внутри полосы a,b, если их точка пересечения лежит в пределах этой полосы.
Для двух множеств отрезков S и S определим множество Int(S,S) как {s,s|(sS,sS)&(s intersect s)}.
D=((s1,s2,s3),a,b), Loc(D,s4)=0, Loc(D,s5)=2 или 3, Int(D,{s4, s5})={{s3, s5}}

Обозначения Inta,b(S) и Inta,b(S,S) будут использоваться для описания подмножеств Int(S) и Int(S,S), состоящих из пересекающихся пар отрезков в пределах полосы a,b. Далее скобки {} используются для определения неупорядоченных множеств, а скобки () используются для определения упорядоченных множеств.

Введем отношение порядка на множестве отрезков s1<as2 если оба отрезка пересекают вертикальную линию x=a и точка пересечения этой прямой с отрезком s1 лежит ниже точки пересечения с s2.


Определение:
Лестница D — это пара (Q,a,b), в которой отрезки из множества Q удовлетворяют следующим условиям :

− любой отрезок из Q содержит полосу a,b;
− нет пересечений отрезков внутри лестницы;
Q упорядочена по отношению <a.

Часть отрезков лестницы внутри полосы будем называть ступеньками.


Определение:
Будем называть лестницу D полностью соотносимой множеству отрезков S, если каждый отрезок из S либо не пересекает полосу a,b, либо пересекает хотя бы одну из ступенек из множества D.


Лемма:
Пусть лестница D полностью соотносима множеству отрезков S, где S состоит из отрезков, пересекающих полосу a,b, тогда |S|Endsa,b(S)+|Int(D,S)|,
где Endsa,b(S) это число вершин отрезков из S, находящихся в пределах полосы a,b.


Определение:
Если точка p отрезка s лежит между ступеньками i и i+1, тогда число i называется местоположением s на лестнице D и обозначается как Loc(D,s)


Утверждение:
Имея лестницу D=(Q,a,b) и множество отрезков S, множество Int(D,S) можно найти за время O(|S|log|Q|+|Int(D,S)|).
Однако, если S упорядочено отношением <x, где x[a,b], тогда можно найти Int(D,S) за время O(|S|+|Q|+|Int(D,S)|).

Алгоритм

Введем несколько дополнительных функций, чтобы упростить основной алгоритм:

Split

Функция Split разделяет входное множество отрезков L, пересекающих некоторую полосу a,b, на подмножества Q и L так, что лестница (Q,a,b) полностью соотносима множеству отрезков L.

Пусть L=(s1,...,sk), где si<asi+1
Splita,b(L,Q,L)
{
    L;Q
    for j=1,...,k do
        if отрезок Sj не пересекает
        последний отрезок из Q внутри полосы a,b
        и при этом содержит её then
            добавить sj в конец Q;
        else
            добавить sj в конец L;
}

Эта функция работает за O(|L|) времени.

Search In Strip

Зная L мы можем найти Inta,b(L) и R используя следующую рекурсивную функцию:

SearchInStripa,b(L,R)
{
    Split(L,Q,L); 
    if L= then
        RQ; 
        return;
    Найдем Inta,b(Q,L);
    SearchInStripa,b(L,R);
    RMergeb(Q,R);
}

Здесь, Mergex(S1,S2) это функция объединения множеств S1 и S2, упорядоченных по отношению <x. Время выполнения SearchInStrip эквивалентно сумме времён каждого её запуска. Очевидно, что время работы i-той функции, будет равно O(|Li|+|Inta,b(Qi,Li)|), где Li,Qi,Li это соответствующие наборы (L0=L,Li+1=Li).

Учитывая лемму, заключаем, что функция SearchInStripa,b(L,R) работает за O(|L|+|Inta,b(L)|).

Предположим, что все отрезки лежат в полосе a,b. Таким образом в самом начале у нас есть пара (S,a,b). Что же дальше происходит: множество S распадается в подмножества Q и S, после чего лестница D=(Q,a,b) становится полностью соотносимой множеству S. Необходимо найти пересечения отрезков из D и S, затем, все пересечения в S. Чтобы найти пересечения отрезков в S, мы режем полосу a,b и множество S по вертикале x=c на полосы a,c, c,b и множества Sls, Srs соответственно, где c является медианой вершин отрезков, между a и b. Затем мы рекурсивно вызываем функцию к парам (Sls,a,c) и (Srs,c,b). Ключевым является тот факт, что согласно лемме |S|Endsa,b(S)+|Int(D,S)|, таким образом, число дополнительных отрезков, появляющихся после разрезаний пропорционально числу найденных пересечений.

Основы алгоритма

Давайте разберемся с алгоритмом более подробно:

Не умаляя общности, предположим, что все пересечения и вершины отрезков имеют разные абсциссы (в конечном счете, их можно будет отсортировать введением дополнительных свойств). Будем рассматривать целые координаты на промежутке [1,2N]. Пусть pi и s(i) будут координатами вершин i-того отрезка.

Основная задача нашего алгоритма, это рекурсивная функция TreeSearch. Мы соединяем каждый вызов функции с узлом некоего двоичного дерева (далее рекурсивное дерево). Мы отмечаем все значения, множества и параметры вызова соответствующим узлом. В результате, мы проанализируем наш алгоритм рекурсивного дерева. Обозначим множество всех вершин рекурсивного дерева за RT, а множество внутренних вершин за V.

IntersectingPairs(Sv,a,b):
    Отсортируем 2N вершин по координатам и
        найдем pi,s(i),i=1,...,2N; SrS0
    TreeSearch(Sr,1,2N);
TreeSearch(Sr,a,b):
{
    if ba=1 then
    {
        L отсортируем Sv по отношению <b;
        SearchInStripa,b(Lv,Rv); 
        return;
    }
    Разделим Sv на Qv и Sv так, что лестница
        Dv(Qv,a,b) будет полностью соотносима множеству Sv;
    Найдем Int(Dv,Sv);
    c(a+b)/2;
    Разделим отрезки из Sv на пересекающих
        полосу a,c Sls(v) и
        полосу c,b Srs(v);
    TreeSearch(Sls(v),a,c);
    TreeSearch(Srs(v),c,b);
}
Sv=(s1,s2,s3,s4,s5), Lv=(s1,s3), Rv=(s3,s4), Iv=(s2,s5)

Отсюда и дальше ls(v), rs(v) и ft(v) означают, соответственно, левого сына, правого сына, и отцовскую вершину узла v. Наша задача показать, что все операции с узлом v происходят за O(|Sv)+|Int(Dv,Sv)|+(avbv)logN), и чтобы показать это, возьмем во внимание, что v|Sv|=O(NlogN+K) (очевидно, что v|Int(Dv,Sv)|K).

Функция TreeSearch похожа на функцию SearchInStrip. Основная разница заключается в том, что SearchInStrip вызывает себя без изменения полосы, когда TreeSearch делит полосу на две части, после чего рекурсивно вызывает себя для них. Другое отличие заключается в том, что множество Sv не упорядочено так же, как L. В результате мы не можем напрямую использовать функцию Split для эффективного деления Sv.

Чтобы решить эту проблему, представим Sv как объединение трех множеств: множества Lv упорядоченного по отношению <a, неупорядоченного множества Iv, и множества Rv упорядоченного по отношению <b. Расположим отрезки из Sv, пересекающие границу x=a во множество Lv, отрезки пересекающие x=b во множество Rv, и внутренние отрезки во множество Iv (пример на рисунке справа).

Теперь мы можем вызвать функцию Split для множества Lv и построить Qv за O(|Lv|)=O(|Sv|) времени. Но мы натыкаемся на новую проблему: передавая множества Lv, Iv и Rv, необходимо найти соответствующие множества сыновей узла v.

Неупорядоченные множества Ils(v) и Irs(v) строятся легко. Множество Lls(v) будет найдено вызовом функции Splita,b(Lv,Qv,Lls(v)) для третьего шага функции TreeSearch. Множество Lrs(v) получается из Rls(v) за линейное время вставкой (если pc левый конец отрезка) или удалением (если pc правый конец отрезка) отрезка s(c). Но как получить Rls(v) из Lv, Rv и Iv без сортировки?

Для листьев мы сделаем проверку вначале, и получим Rv из Lv. Пусть Lv и Iv известны, и все сыновья узла v - листья. Для начала запустим функцию Split(Lv,Qv,Lls(v)) и найдем Qv и Lls(v). Теперь мы должны найти Int(Ds,Sv)=Int(Dv,Lls(v))Int(Dv,Iv)Int(Dv,Rrs(v)), но мы не знаем Rrs(v), и соответственно можем найти только Int(Dv,Lls(v))Int(Dv,Iv). Применим SearchInStrip к множеству Lls(v) и получим Rls(v). Множество Lrs(v) получается из Rls(v) вставкой или удалением отрезка s(c). Применим SearchInStrip к Lrs(v) и найдем Rrs(v). Теперь можем продолжить вычисление Int(Dv,Rrs(v)) и получим Rv слиянием Qv и Rrs(v).

Конечная функция будет выглядеть так:

IntersectingPairs(S0)
{
    Отсортируем 2N концов отрезков по абсциссе
        и найдем pi, s(i) где i=1,...,2N;
    Lr(s(1)); IrS0({s(1)}{s(2N)});
    TreeSearch(Lr,Ir,1,2N,Rr);
}
TreeSearch(Lv,Iv,a,b,Rv)
{
    if ba=1 then
    {
        SearchInStripa,b(Lv,Rv);
        return;
    }
    Splita,b(Lv,Qv,Lls(v));
    Dv(Qv,a,b);
    Найдем Int(Dv,Lls(v));
    c(a+b)/2;
   Разделяем отрезки из Iv
       внутренние для полосы a,c во множество Ils(v)
       внутренние для полосы c,b во множество Irs(v)
    TreeSearch(Lls(v),Ils(v),a,c,Rls(v));
    if pc левый конец отрезка s(c) then
        Lrs(v) вставить s(c) в Rls(v)
    else
        Lrs(v) удалить s(c) из Rls(v)
    TreeSearch(Lrs(v),Irs(v),c,b,Rrs(v));
    Найдем Int(Dv,Rrs(v));
    for sIv do
        Найдем Loc(Dv,s) используя двоичный поиск;
    Найдем Int(Dv,Iv) используя значения, полученные шагом выше;
    RvMergeb(Qv,Rrs(v));
}

Заметим, что нахождение Int(Dv,Rrs(v)) надо делать аккуратно, так как множества Rrs(v) и Lls(v) могут иметь одни и те же отрезки (которые пересекают a,b). Мы нашли их пересечения с Dv на 3ем шаге, и не должны вывести эти пересечения снова.

Для начала рассчитаем место, необходимое для выполнения алгоритма. Алгоритм использует рекурсивную функцию TreeSearch. Последовательность вызовов функции может занять память. Эта последовательность может быть представлена как путь корня рекурсивного дерева, до узла. Назовем этот узел, и соответствующий вызов активным. Активный вызов занимает O(N) памяти, каждый его "предок" занимает O(|Iv|+|Qv|) памяти, а остальные структуры используют O(N). Очевидно, что любой путь pt от корня рекурсивного дерева до какого-то узла vpt(|Iv+|Qv|)=O(N)(|Iv|bvav(bft(v)aft(v))/2).

В итоге для работы алгоритма требуется O(N) памяти.

Время работы

Лемма (#2):
vV |Sv|avbv+|Int(Dv,Sv)|.
Доказательство:
Утверждение напрямую вытекает из леммы (далее лемма №1) и очевидного факта, что для любого множества SS0, количество концов отрезков, лежащих в полосе av,bv, меньше чем bvav.
Теорема (#1):
vV|Sv|2NlogN+1+K
Доказательство:
Утверждение напрямую вытекает из леммы №2 и следующего отношения v(bvav)2NlogN+1.
Теорема (#2):
vRT|Sv|N4logN+5+2K
Доказательство:
Для всех узлов, кроме корня r имеет место выражение |Sv||Sft(v)|, следовательно vRT|Sv||Sr|+vRTr|Sft(v)|N+2vV|sv|N4logN+5+2K.

Начальная сортировка и инициализация множеств Lr и Ir может быть произведена за O(NlogN) времени. Время работы функции TreeSearch является суммой длительностей всех его вызовов. Каждый вызов от внешних узлов добавляет к этой сумме O(|Lv|+|Inta,b(Lv)|). Для внутренних же узлов, время требуемое для выполнения 10го шага алгоритма равно O(|Iv|logN), а для остальных O(|Sv|+|Int(Dv,Sv)|). Если мы все это сложим, то придем к выводу, что наш алгоритм работает за O(Nlog2N+K). Заметим, что его скорость можно увеличить до O(NlogN+K), если не будем учитывать время нахождения Loc(Dv,s).

Соответственно в оптимальном алгоритме Балабана Loc(Dv,s) находится за O(1).

Примечания

Литература

Т.Вознюк, В.Терещенко — К построению эффективного решения задачи пересечения отрезков
Ф.Препарата, М.Шеймос — Вычислительная геометрия