MAXimal | |
добавлено: 5 Aug 2011 1:55 Содержание [скрыть] Поиск мостов в режиме онлайнПусть дан неориентированный граф. Мостом называется такое ребро, удаление которого делает граф несвязным (или, точнее, увеличивает число компонент связности). Требуется найти все мосты в заданном графе. Неформально эта задача ставится следующим образом: требуется найти на заданной карте дорог все "важные" дороги, т.е. такие дороги, что удаление любой из них приведёт к исчезновению пути между какой-то парой городов. Описываемый здесь алгоритм является онлайновым, что означает, что входной граф не является известным заранее, а рёбра в него добавляются по одному, и после каждого такого добавления алгоритм пересчитывает все мосты в текущем графе. Иными словами, алгоритм предназначен для эффективной работы на динамическом, изменяющемся графе. Более строго, постановка задачи следующая. Изначально граф пустой и состоит из вершин. Затем поступают запросы, каждый из которых — это пара вершин , которые обозначают ребро, добавляемое в граф. Требуется после каждого запроса, т.е. после добавления каждого ребра, выводить текущее количество мостов в графе. (При желании можно поддерживать и список всех рёбер-мостов, а также явно поддерживать компоненты рёберной двусвязности.) Описываемый ниже алгоритм работает за время , где — число запросов. Алгоритм основан на структуре данных "система непересекающихся множеств". Приведённая реализация алгоритма, впрочем, работает за время , поскольку использует в одном месте упрощённую версию системы непересекающихся множеств без ранговой эвристики. АлгоритмИзвестно, что рёбра-мосты разбивают вершины графа на компоненты, называемые компонентами рёберной двусвязности. Если каждую компоненту рёберной двусвязности сжать в одну вершину, и оставить только рёбра-мосты между этими компонентами, то получится ациклический граф, т.е. лес. Описываемый ниже алгоритм поддерживает в явном виде этот лес компонент рёберной двусвязности. Понятно, что изначально, когда граф пустой, он содержит компонент рёберной двусвязности, не связанных никак между собой. При добавлении очередного ребра может возникнуть три ситуации:
Следовательно, вся задача сводится к эффективной реализации всех этих операций над лесом компонент. Структуры данных для хранения лесаВсё, что нам понадобится из структур данных, — это система непересекающихся множеств. На самом деле, нам понадобится делать два экземпляра этой структуры: одна будет для поддержания компонент связности, другая — для поддержания компонент рёберной двусвязности. Кроме того, для хранения структуры деревьев в лесу компонент двусвязности для каждой вершины будем хранить указатель на её предка в дереве. Будем теперь последовательно разбирать каждую операцию, которую нам надо научиться реализовывать:
РеализацияПриведём здесь итоговую реализацию всего алгоритма. В целях простоты система непересекающихся множеств для компонент двусвязности написана без ранговой эвристики, поэтому итоговая асимптотика составит на запрос в среднем. (О том, как достичь асимптотики , написано выше в пункте "Сжатие цикла".) Также в данной реализации не хранятся сами рёбра-мосты, а хранится только их количество — см. переменная . Впрочем, при желании не составит никакого труда завести из всех мостов. Изначально следует вызвать функцию , которая инициализирует две системы непересекающихся множеств (выделяя каждую вершину в отдельное множество, и проставляя размер, равный единице), проставляет предков . Основная функция — это , которая обрабатывает запрос на добавление нового ребра. Константе следует задать значение, равное максимально возможному количеству вершин во входном графе. Более подробные пояснения к данной реализации см. ниже. const int MAXN = ...; int n, bridges, par[MAXN], bl[MAXN], comp[MAXN], size[MAXN]; void init() { for (int i=0; i<n; ++i) { bl[i] = comp[i] = i; size[i] = 1; par[i] = -1; } bridges = 0; } int get (int v) { if (v==-1) return -1; return bl[v]==v ? v : bl[v]=get(bl[v]); } int get_comp (int v) { v = get(v); return comp[v]==v ? v : comp[v]=get_comp(comp[v]); } void make_root (int v) { v = get(v); int root = v, child = -1; while (v != -1) { int p = get(par[v]); par[v] = child; comp[v] = root; child=v; v=p; } size[root] = size[child]; } int cu, u[MAXN]; void merge_path (int a, int b) { ++cu; vector<int> va, vb; int lca = -1; for(;;) { if (a != -1) { a = get(a); va.pb (a); if (u[a] == cu) { lca = a; break; } u[a] = cu; a = par[a]; } if (b != -1) { b = get(b); vb.pb (b); if (u[b] == cu) { lca = b; break; } u[b] = cu; b = par[b]; } } for (size_t i=0; i<va.size(); ++i) { bl[va[i]] = lca; if (va[i] == lca) break; --bridges; } for (size_t i=0; i<vb.size(); ++i) { bl[vb[i]] = lca; if (vb[i] == lca) break; --bridges; } } void add_edge (int a, int b) { a = get(a); b = get(b); if (a == b) return; int ca = get_comp(a), cb = get_comp(b); if (ca != cb) { ++bridges; if (size[ca] > size[cb]) { swap (a, b); swap (ca, cb); } make_root (a); par[a] = comp[a] = b; size[cb] += size[a]; } else merge_path (a, b); } Прокомментируем код более подробно. Система непересекающихся множеств для компонент двусвязности хранится в массиве , а функция, возвращающая лидера компоненты двусвязности — это . Эту функцию используется много раз в остальном коде, поскольку нужно помнить о том, что после сжатия нескольких вершин в одну все эти вершины перестают существовать, а вместо них существует только их лидер, у которого и хранятся корректные данные (предок , предок в системе непересекающихся множеств для компонент связности, и т.д.). Система непересекающихся множеств для компонент связности хранится в массиве , также есть дополнительный массив для хранения размеров компонент. Функция возвращает лидера компоненты связности (который на самом деле является корнем дерева). Функция переподвешивания дерева работает, как и было описано выше: она идёт от вершины по предкам до корня, каждый раз перенаправляя предка в обратную сторону (вниз, по направлению к вершине ). Также обновляется указатель в системе непересекающихся множеств для компонент связности, чтобы он указывал на новый корень. После переподвешивания у нового корня проставляется размер компоненты связности. Обратим внимание, что при реализации мы каждый раз вызываем функцию , чтобы получить доступ именно к лидеру компоненты сильной связности, а не к какой-то вершине, которая возможно уже была сжата. Функция обнаружения и сжатия пути , как и было описано выше, ищет LCA вершин и , для чего поднимается от них параллельно вверх, пока какая-то вершина не встретится во второй раз. В целях эффективности пройденные вершины помечаются с помощью техники "числового used", что работает за вместо применения . Пройденные пути сохраняются в векторах и , чтобы потом пройтись по ним второй раз до LCA, получив тем самым все вершины цикла. Все вершины цикла сжимаются, путём присоединения их к LCA (здесь возникает асимптотика , поскольку при сжатии мы не используем ранговую эвристику). Попутно считается число пройденных рёбер, которое равно количеству мостов в обнаруженном цикле (это количество отнимается от ). Наконец, функция обработки запросов определяет компоненты связности, в которых лежат вершины и , и если они лежат в разных компонентах связности, то меньшее дерево переподвешивается за новый корень и затем присоединяется к большему дереву. Иначе же, если вершины и лежат в одном дереве, но в разных компонентах двусвязности, то вызывается функция , которая обнаружит цикл и сожмёт его в одну компоненту двусвязности.
|