E029. Поиск мостов

e-maxx algorithm original: C/C++ #algorithm #connectivity #emaxx #graph #search #tree
선택한 UI 언어에 맞게 문제 텍스트를 러시아어에서 번역합니다. 코드는 변경하지 않습니다.

Источник: e-maxx.ru/algo, страница PDF 92.

Пусть дан неориентированный 그래프. Мостом называется такое edge, удаление которого делает 그래프 несвязным (или, точнее, увеличивает number компонент связности). it is required find все мосты в заданном 그래프е. Неформально эта 문제 ставится следующим образом: it is required find на заданной карте дорог все "важные" дороги, т.е. такие дороги, что удаление любой из них приведёт к исчезновению пути между какой-то парой городов. Ниже мы опишем 알고리즘, основанный на поиске в глубину, и работающий за время

, где

количество вершин,

— рёбер в 그래프е. Заметим, что на сайте также описан онлайновый 알고리즘 поиска мостов — в отличие от описанного здесь 알고리즘а, онлайновый 알고리즘 умеет поддерживать все мосты 그래프а в изменяющемся 그래프е (имеются в виду добавления новых рёбер).

알고리즘

Запустим обход в глубину из произвольной вершины 그래프а; обозначим её через

. Заметим следующий

факт (который несложно доказать):

● Пусть мы находимся в обходе в глубину, просматривая сейчас все рёбра из вершины

. Тогда, если текущее

edge

таково, что из вершины

и из любого её потомка в дереве обхода в глубину нет обратного ребра

в вершину

или какого-либо её предка, то это edge является мостом. В противном случае оно мостом не является. (В самом деле, мы этим 문제 설명м проверяем, нет ли другого пути из

в

, кроме как спуск по ребру

дерева обхода в глубину.)

Теперь осталось научиться проверять этот факт для каждой вершины эффективно. Для этого воспользуемся "временами 입력а в вершину", вычисляемыми 알고리즘ом поиска в глубину.

Итак, пусть

— это время захода поиска в глубину в вершину

. Теперь введём 배열

, который

и позволит нам отвечать на вышеописанные запросы. Время

равно минимуму из времени захода в саму

вершину

, времён захода в каждую вершину

, являющуюся концом некоторого обратного ребра

, а

также из всех значений

для каждой вершины

, являющейся непосредственным сыном

в дереве поиска: (здесь "back edge" — обратное edge, "tree edge" — edge дерева)

Тогда, из вершины

или её потомка есть обратное edge в её предка тогда и только тогда, когда найдётся такой сын

, что

. (Если

, то это означает, что найдётся обратное edge,

приходящее точно в

; если же

, то это означает наличие обратного ребра в какого-либо

предка вершины

.)

Таким образом, если для текущего ребра

(принадлежащего дереву поиска) выполняется

,

то это edge является мостом; в противном случае оно мостом не является.

구현

Если говорить о самой реализации, то здесь нам нужно уметь различать три случая: когда мы идём по ребру дерева поиска в глубину, когда идём по обратному ребру, и когда пытаемся пойти по ребру дерева в обратную сторону. Это, соответственно, случаи:

— критерий ребра дерева поиска;

— критерий обратного ребра;

— критерий прохода по ребру дерева поиска в обратную сторону. Таким образом, для реализации этих критериев нам надо передавать в функцию поиска в глубину вершину-предка текущей вершины.

const int MAXN = ...;

vector<int> g[MAXN];

bool used[MAXN];

int timer, tin[MAXN], fup[MAXN];

void dfs (int v, int p = -1) {

used[v] = true;

tin[v] = fup[v] = timer++;

for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (to == p)  continue;
if (used[to])

fup[v] = min (fup[v], tin[to]);

else {

dfs (to, v);

fup[v] = min (fup[v], fup[to]);

if (fup[to] > tin[v])

IS_BRIDGE(v,to);

} } }

void find_bridges() {

timer = 0;

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

used[i] = false;

for (int i=0; i<n; ++i)
if (!used[i])

dfs (i);

}

Здесь основная функция для вызова — это

— она производит необходимую инициализацию и

запуск обхода в глубину для каждой компоненты связности 그래프а.

При этом

— это некая функция, которая будет реагировать на то, что edge

является мостом, на예제, выводить это edge на экран.

Константе

в самом начале кода следует задать значение, равное максимально возможному числу вершин во 입력ном 그래프е. Стоит заметить, что эта 구현 некорректно работает при наличии в 그래프е кратных рёбер: она фактически не обращает внимания, кратное ли edge или оно единственно. Разумеется, кратные рёбра не должны 입력ить в

ответ, поэтому при вызове

можно проверять дополнительно, не кратное ли edge мы хотим добавить в ответ. Другой способ — более аккуратная работа с предками, т.е. передавать в

не вершину-предка, а номер

ребра, по которому мы вошли в вершину (для этого надо будет дополнительно хранить номера всех рёбер).

Задачи в online judges

Список задач, в которых it is required искать мосты:

● UVA #796 "Critical Links" [Complexity: низкая]

● UVA #610 "Street Directions" [Complexity: средняя]

C# 해법

자동 초안, 제출 전 검토
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    const int MAXN = ...;
    List<int> g[MAXN];
    bool[] used = new bool[MAXN];
    int timer, tin[MAXN], fup[MAXN];
    void dfs (int v, int p = -1) {
            used[v] = true;
            tin[v] = fup[v] = timer++;
            for (size_t i=0; i<g[v].size(); ++i) {
                    int to = g[v][i];
                    if (to == p)  continue;
                    if (used[to])
                            fup[v] = min (fup[v], tin[to]);
                    else {
                            dfs (to, v);
                            fup[v] = min (fup[v], fup[to]);
                            if (fup[to] > tin[v])
                                    IS_BRIDGE(v,to);
                    }
            }
    }
    void find_bridges() {
            timer = 0;
            for (int i=0; i<n; ++i)
                    used[i] = false;
            for (int i=0; i<n; ++i)
                    if (!used[i])
                            dfs (i);
    }
}

C++ 해법

매칭됨/원본
const int MAXN = ...;
vector<int> g[MAXN];
bool used[MAXN];
int timer, tin[MAXN], fup[MAXN];
void dfs (int v, int p = -1) {
        used[v] = true;
        tin[v] = fup[v] = timer++;
        for (size_t i=0; i<g[v].size(); ++i) {
                int to = g[v][i];
                if (to == p)  continue;
                if (used[to])
                        fup[v] = min (fup[v], tin[to]);
                else {
                        dfs (to, v);
                        fup[v] = min (fup[v], fup[to]);
                        if (fup[to] > tin[v])
                                IS_BRIDGE(v,to);
                }
        }
}
void find_bridges() {
        timer = 0;
        for (int i=0; i<n; ++i)
                used[i] = false;
        for (int i=0; i<n; ++i)
                if (!used[i])
                        dfs (i);
}

Java 해법

자동 초안, 제출 전 검토
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    const int MAXN = ...;
    ArrayList<Integer> g[MAXN];
    boolean used[MAXN];
    int timer, tin[MAXN], fup[MAXN];
    void dfs (int v, int p = -1) {
            used[v] = true;
            tin[v] = fup[v] = timer++;
            for (size_t i=0; i<g[v].size(); ++i) {
                    int to = g[v][i];
                    if (to == p)  continue;
                    if (used[to])
                            fup[v] = min (fup[v], tin[to]);
                    else {
                            dfs (to, v);
                            fup[v] = min (fup[v], fup[to]);
                            if (fup[to] > tin[v])
                                    IS_BRIDGE(v,to);
                    }
            }
    }
    void find_bridges() {
            timer = 0;
            for (int i=0; i<n; ++i)
                    used[i] = false;
            for (int i=0; i<n; ++i)
                    if (!used[i])
                            dfs (i);
    }
}

Материал разбит как 알고리즘ическая 문제: изучить постановку, понять асимптотику и реализовать 알고리즘 на выбранном языке.

Vacancies for this task

활성 채용 with overlapping task tags are 표시됨.

전체 채용
아직 활성 채용이 없습니다.