Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lektsii_TRPO / Krusk+Prima_lec.doc
Скачиваний:
63
Добавлен:
12.03.2015
Размер:
1.14 Mб
Скачать

7.2. Построение остовного дерева минимального веса.

Среди всех каркасов графа в практическом плане часто интересует каркас минимального веса. Например, если граф является моделью для решения задачи проектирования сети связи между несколькими населенными пунктами – требуется минимизировать длину (стоимость) кабеля связи. В этом случае граф G = (V,E) будет связным взвешенным неориентированным, таким образом, каждому ребру приписано некоторое число (вес) (рис.7.4) и необходимо найти каркас с минимальным суммарным весом A = (V,B), где BE (рис.7.5).

Рассмотрим общую схему работы алгоритмов построения минимального остовного дерева с использованием жадной стратегии. Итак, пусть дан связный неориентированный граф G c n вершинами и весовая функция w: E R.

Искомый остов строится постепенно. Алгоритм использует некоторый ациклический подграф А исходного графа G, который называется промежуточным остовным лесом. Изначально G состоит из n вершин-компонент, не соединенных друг с другом (n деревьев из одной вершины). На каждом шаге в A добавляется одно новое ребро. Граф A всегда является подграфом некоторого минимального остова. Очередное добавляемое ребро e=(u,v) выбирается так, чтобы не нарушить этого свойства: A U {e} тоже должно быть подграфом минимального. Такое ребро называется безопасным (ребро G называется безопасным (safe), если для некоторой компоненты A это ребро является самым лёгким из всех рёбер, соединяющих вершины этой компоненты с оставшимися вершинами).

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

MIN_OSTOV(G,w) 1: A ← 0 2: While (пока) A не является остовом 3:     Do найти безопасное ребро (u,v) E для A 4:          A ← A U {(u,v)} 5: Return A

По определению A, он должен оставаться подграфом некоторого минимального остова после любого числа итераций. Конечно, главный вопрос состоит в том, как искать безопасное ребро на шаге 3. Понятно, что такое ребро всегда существует, если A еще не является минимальным остовом (любое ребро остова, не входящее в A). Заметим, что так как A не может содержать циклов, то на каждом шаге ребром соединяются различные компоненты связности (изначально все вершины в отдельных компонентах, в конце A – одна компонента). Таким образом цикл выполняется (n-1) раз.

У задачи построения минимального остовного дерева длинная и богатая история[2 ]R. L. Graham and P. Hell. On the history of the minimum spanning tree problem. Annals of the History of Computing, 7(1): 43-57, 1985. Впервые работа на эту тему был опубликован в 1926 году Отакаром Борувкой в качестве метода нахождения оптимальной электрической сети в Моравии. В настоящее время наиболшую известность получили алгоритмы Крускала и Прима.

Алгоритм Крускала. Первый из предлагаемых алгоритмов принадлежи классу так называемых жадных алгоритмов. Этот алгоритм пытается найти решение поставленной задачи в лоб, стараясь найти минимальное остовное дерево последовательным отбором ребер с минимальным весом, следя за тем, чтобы получающийся граф не имел циклов. Разумеется, для этого необходимо сначала отсортировать все ребра графа по возрастанию их весов. Лучше всего, если граф с самого начала представлен списком ребер, в этом случае сортировка будет наиболее естественной. Но даже и в других случаях сортировка ребер графа по весу занимает не слишком много времени - в худшем случае mlog2m, где m - количество ребер графа.

После сортировки ребер по весу строящееся остовное дерево организуется в виде отдельных фрагментов этого дерева, в которые в каждый момент времени включены все вершины графа. В начале работы каждая вершина графа представляет собой отдельную компоненту, а соединяющих их ребер пока вообще нет. Каждое вновь включаемое ребро соединяет две отдельные компоненты в одну, таким образом, полное остовное дерево будет построено после n-1 включения ребра. Разумеется, очередное ребро можно включить в дерево, только если его концы принадлежат разным компонентам, в противном случае в компоненте образуется цикл. Таким образом, если очередное ребро соединяет вершины, принадлежащие одной связной компоненте, то такое ребро не включается в строящееся остовное дерево и игнорируется алгоритмом.

Рассмотрим реализацию алгоритма на примере графа на рис.7.4[Окулов]. Потребуется массив меток вершин графа и массив ребер отсортированных по весам (рис. 7.6).

Начальные значения элементов массива меток M равны номерам соответствующих вершин. Ребро выбирается в каркас в том случае, если вершины, соединяемые им, имеют разные значения меток. В этом случае циклы не образуются. Для примера, приведенного выше, процесс изменения Mark показан на рис. .

Номер итерации

Ребро

Массив М

Нач. состояние

(1 2 3 4 5 6)

1

< 4, 1>

(1 2 3 1 5 6)

2

< 6, 1>

(1 2 3 1 5 1)

3

< 5, 2>

(1 2 3 1 2 1)

4

< 6, 3>

(1 2 1 1 2 1)

5

< 6, 5>

(1 1 1 1 1 1)

Рис. 7.7. Процесс изменения массива меток М

Реализация алгоритма:

1. Ищем ребро < v, w > с различными метками инцидентных ему вершин.

2. Копируем метку М[ v] M[w] , если М[v] < M[w] и, наоборот,

М[w] M[v] , если М[w] < M[v].

3. Повторяем эти действия n-1 раз (по числу ребер, которые образуют каркас).

В конце концов все элементы массива М приобретут значение метки равной 1 (рис. 7.7)

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

На рис. рис. 7.8. представлена последовательность этапов при построении компонент остовного дерева согласно жадному алгоритму. В качестве исходного графа выбран граф рис.7.4. Первоначально остовное дерево состоит из отдельных вершин исходного графа, жадный алгоритм постепенно добавляет к нему ребра, начиная с ребер с наименьшим весом. На рис. 7.8, б показаны 5 последовательных этапов построения остовного дерева согласно жадному алгоритму. На каждом этапе в остовное дерево включается новое ребро. Ребра, замыкающие цикл в уже построенной части, игнорируются алгоритмом.

Procedure Marker ( a, b :Integer ); { меньшую метку распространяет на все

вершины, инцидентные данному ребру (a,b)}

Var i, c :Integer;

Begin

If a > b Then Begin c := a; a := b; b := c; End; { делаем а <b }

For i := 1 To n Do

If M[ i ] = b Then M[ i ] := a;

End;

Procedure Kruskal;

Var i, j, v, w :Integer;

Begin

СОРТИРОВКА ребер R по возрастанию их весов;

For i :=1 To n Do M[ i ] := i;

For i :=1 To n-1 Do Begin

For j := 1 To n Do Begin

v :=R[ 1, j ]; w := R[ 2, j ];

If M[ v ] < > M[ w ] Then Break;

End;

Write( v:3, w:3); {вывод ребра}

Marker ( M[ v ], M[ w ] ); { меньшую метку распространяет на все}

End; {вершины, инцидентные данному ребру (a,b)}

End;

Общее время работы алгоритма Крускала составляет O(ElogE) (основное время уходит на сортировку). Скорость работы жадного алгоритма определяется скоростью, с которой будут выбраны и отсортированы все ребра графа, т. к. вся остальная работа обычно занимает меньше времени. Тем не менее для больших графов, построение и проверка деревьев компонент строящегося остовного дерева может занимать значительное время, если они вырождаются в линейные списки вершин, что вполне возможно в некоторых специфических ситуациях.

Алгоритм Прима. В этом алгоритме построение остовного дерева начинается с одной вершины, к которой затем добавляются ребра таким образом, чтобы каждое новое ребро одним своим концом опиралось в уже построенную часть дерева, а другой конец лежал бы в множестве еще не присоединенных к дереву вершин. Из всех таких ребер на каждом шаге выбирается ребро с наименьшим весом. Алгоритм Прима следует общей схеме алгоритма построения минимального остовного дерева: на каждом шаге мы добавляем к строящемуся остову безопасное ребро. Как и прежде воспользуемся «жадным» алгоритмом. Жадные алгоритмы действуют, используя в каждый момент лишь часть исходных данных и принимая лучшее решение на основе этой части. В нашем случае мы будем на каждом шаге рассматривать множество ребер, допускающих присоединение к уже построенной части остовного дерева, и выбирать из них ребро с наименьшим весом. Повторяя эту процедуру, мы получим остовное дерево с наименьшим весом. Отличие этого метода от метода Крускала заключается в том, что на каждом шаге строится дерево, а не ациклический граф (граф без циклов).

Для реализации введем две переменные множественного типа S0 и S1. Первоначально в S1 находятся все вершины графа, кроме начальной, а в S0 только начальная. Далее ищется ребро минимального веса, соединяющего вершину из S0 и одну из вершин v, принадлежащую S1. Найденное ребро включается в Q, а вершина v исключается из S1 и включается в S0. Далее повторяется поиск следующего ребра до тех пор, пока S1 не станет пустым.

На рис. 7.9 приведен пример построения каркаса по алгоритму Прима. Программная реализация алгоритма приведена ниже. В ней используется вспомогательная функция Extract_Min, которая ищет кратчайшее ребро, соединяющее вершины из S0 и S1.

S0, S1 :Set Of Byte; { S0 - множество выбранных вершин; }

{ S1 - множество невыбранных вершин;}

Function Extract_Min :Byte; {возвращает вершину v из S1 - самую }

Var i, j, w, v, Min :Integer; { « близкую» к множеству вершин S0 }

Begin Min := MaxInt;

For i := 0 To n-1 Do {перебор вершин в S0}

If i In S0

Then For j := 0 To n-1 Do {перебор вершин в S1}

If j In S1

Then If ( A[ i, j ] <> 0 ) And ( A[ i, j ] < Min )

Then Begin

Min := A[ i, j ];

w := i; v:=j;

End;

WriteLn( w :3, v :3 );

Extract_Min := v;

End;

Procedure Prima ( s :Byte ); { sначальная вершина}

Var v :Byte;

Begin

S0 := [ s ];

S1 := [ 0..n – 1 ] - [ s ];

While S1 <> [ ] Do Begin

v := Extract_Min;

S0 := S0 + [ v ];

S1 := S1 - [ v ];

End;

End;

Время работы алгоритма Прима зависит от того, как реализована функция Extract_Min. Если вместо множеств S0 и S1 использовать очередь с приоритетами, то это время составит O(ElogV).

Алгоритмы имеют разную производительность на различных графах. Скорость работы алгоритма Крускала зависит, прежде всего, от количества ребер в графе и слабо зависит от количества вершин. Напротив, скорость работы алгоритма Прима определяется количеством вершин и слабо зависит от числа ребер. Следовательно, алгоритм Крускала следует применять в случае неплотных или разреженных графов, у которых количество ребер мало по сравнению с количеством ребер у полного графа. Если известно, что ребер в графе достаточно много, то для поиска минимального остовного дерева лучше применять алгоритм Прима.

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

9

Соседние файлы в папке Lektsii_TRPO