Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Теория графов / Филатова 311 ТГ.pdf
Скачиваний:
13
Добавлен:
18.08.2022
Размер:
247.39 Кб
Скачать

2 Алгоритм Косарайю

2.1 Теоретическое обоснование алгоритма

Поиск компонент сильной связности стал возможен благодаря поиску в глубину на ориентированном графе. Описываемый здесь алгоритм был предложен независимо Косарайю в 1979 году.

Чтобы найти области сильной связности, сначала выполняется поиск в глубину на обращении исходного графа, вычисляя порядок выхода из вершин. Затем мы используем обращение этого порядка, чтобы выполнить поиск в глубину на исходном графе (в очередной раз берём вершину с максимальным номером, полученным при обратном проходе). Деревья в лесе DFS, которые выбираются в результате, представляют собой сильные компоненты. [4]

Этот алгоритм, работает на двух сериях поисков в глубину(допустим обход в ширину), и потому выполняется за время O(n + m)

На первом шаге мы проходим по всем вершинам графа и из каждой не посещённой вершины вызываем поиск в глубину. При этом для каждой вершины v запомним время выхода tout[v]. Эти времена выхода играют ключевую роль в алгоритме, и эта роль выражена в изложенной ниже теореме.

Шаг 1 алгоритма представляет собой не что иное, как топологическую сортировку графа G (фактически именно это и означает сортировка вершин по времени выхода)

Сначала введём обозначение: время выхода tout[C] из компоненты C

сильной связности определим как максимальное значение из tout[v] для всех v 2 C. Время входа в каждую вершину – tin[v], и определим время входа tin[C] для каждой компоненты сильной связности как минимальное значение из tin[v] для всех v 2 C.

Теорема. Пусть C и C0 — две различные компоненты сильной связности, и пусть в графе конденсации между ними есть ребро (C; C0). Тогда tout[C] > tout[C0]. [5]

При доказательстве возможны два различных случая в зависимости от того, в какую из компонент первой зайдёт обход в глубину, то есть в зависимости от соотношения между tin[C] и tin[C0]:

Допустим, первой была достигнута компонента C. Это означает, что в какой-то момент времени обход в глубину заходит в некоторую вершину v

компоненты C, при этом все остальные вершины компонент C и C0 ещё не

7

посещены. Но, так как по условию в графе конденсаций есть ребро (C; C0), то из вершины v будет достижима не только вся компонента C, но и вся компонента C0. Это означает, что при запуске из вершины v обход в глубину посетит все вершины компонент C и C0, а, значит, они станут потомками по отношению к v в дереве обхода в глубину, то есть для любой вершины u

2 C [ C0, u 6= v будет выполнено tout[v] > tout[u].

Предположим, что первой была достигнута компонента C0.Получается, в какой-то момент времени обход в глубину заходит в некоторую вершину v 2 C0, причём все остальные вершины компонент C и C0 не посещены. Поскольку по условию в графе конденсаций существовало ребро (C; C0), то, вследствие ацикличности графа конденсаций, не существует обратного пути

C0 67!C,то есть обход в глубину из вершины v не достигнет вершин C. Это означает, что они будут посещены обходом в глубину позже, откуда и следует tout[C] > tout[C0].

Доказанная теорема является основой алгоритма поиска компонент сильной связности. Из неё следует, что любое ребро (C; C0) в графе конденсаций выходит из компоненты с большей величиной tout в компоненту с меньшей величиной.

Если мы отсортируем все вершины v 2 V в порядке убывания времени выхода tout[v], то первой окажется некоторая вершина u, принадлежащая "корневой"компоненте сильной связности,то есть в которую не входит ни одно ребро в графе конденсаций. Теперь нам хотелось бы запустить такой обход из этой вершины u, который бы посетил только эту компоненту сильной связности и не оказался в другой; научившись это делать, мы сможем постепенно выделить все компоненты сильной связности: удалив из графа вершины первой выделенной компоненты, мы снова найдём среди оставшихся вершину с наибольшей величиной tout и снова запустим из неё обход, и так далее. [6]

Чтобы научиться делать такой обход, рассмотрим транспонированный граф GT , т.е. граф, полученный из G инвертированием. Инвертированием ориентированного графа назовем процедуру, в ходе которой поменяем направление каждого ребра на противоположное. Понятно, что в этом графе будут те же компоненты сильной связности, что и в исходном графе. Более того, граф конденсации (GT )SCC для него будет равен транспонированному графу конденсации исходного графа GSCC. Это означает, что теперь из рассматри-

8

ваемой нами "корневой"компоненты уже не будут выходить рёбра в другие компоненты.

Таким образом, чтобы обойти всю "корневую"компоненту сильной связности, содержащую некоторую вершину v, достаточно запустить обход из вершины v в графе GT . Этот обход посетит все вершины этой компоненты сильной связности и только их. Как уже говорилось, дальше мы можем мысленно удалить эти вершины из графа, находить очередную вершину с максимальным значением tout[v] и запускать обход на транспонированном графе из неё, и так далее.

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

Отсортировать граф топологически, то есть:

выполнить DFS, сохраняя время выхода для каждой вершины;

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

Транспонировать граф.

Обойти граф с помощью DFS или BFS, выбирая вершины согласно топологическому порядку.

все достижимые вершины добавляются в список, соответствующий текущей компоненте связности;

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

2.2 Асимптотика

Для того, чтобы инвертировать все ребра в графе, представленном в виде списка потребуется O(V + E) действий. Для матричного представления графа не нужно выполнять никакие действия для его инвертирования. [3] Количество ребер в инвертированном равно количеству ребер в изначальном графе, поэтому поиск в глубину будет работать за O(V + E) Поиск в глубину в исходном графе выполняется за O(V + E). В итоге получаем, что время работы алгоритма O(V + E).

2.3 Реализация

1 vector < vector<int> > g, gr;

2 vector<char> used;

9

42 }

3 vector<int> order, component;

4

5 void dfs1 (int v) {

6used[v] = true;

7for (size_t i=0; i<g[v].size(); ++i)

8

if (!used[ g[v][i] ])

9

dfs1 (g[v][i]);

10order.push_back (v);

11}

12void dfs2 (int v) {

13used[v] = true;

14component.push_back (v);

15for (size_t i=0; i<gr[v].size(); ++i)

16

 

if (!used[ gr[v][i] ])

17

 

 

 

dfs2 (gr[v][i]);

18

}

 

 

 

 

 

 

 

 

 

 

 

 

19

 

 

 

 

 

 

 

 

 

 

 

 

 

20

int main() {

21

int n;

22

 

 

 

 

 

 

 

 

 

 

 

 

 

...

чтение

n ...

23

 

 

 

 

 

 

 

 

 

 

 

 

 

for (;;) {

24

 

int a, b;

25

 

 

 

 

 

 

 

 

 

 

 

 

 

...

чтение

 

очередного

 

ребра

(a,b) ...

26

 

 

 

 

 

 

 

 

 

 

 

 

 

g[a].push_back (b);

27

 

gr[b].push_back (a);

28

}

 

 

 

 

 

 

 

 

 

 

 

 

29

used.assign (n, false);

30

for (int i=0; i<n; ++i)

31

 

if (!used[i])

32

 

 

 

dfs1 (i);

33

used.assign (n, false);

34

for (int i=0; i<n; ++i) {

35

 

int v = order[n-1-i];

36

 

if (!used[v]) {

37

 

 

 

dfs2 (v);

38

 

 

 

 

 

 

 

 

 

 

 

...

вывод

 

очередной

component ...

39

 

 

 

 

 

 

 

 

 

 

 

component.clear();

40

}

 

 

 

 

 

 

 

 

 

 

 

41

}

 

 

 

 

 

 

 

 

 

 

 

 

10

Соседние файлы в папке Теория графов