Объяснение алгоритма нахождения точек сочленения или срезанных вершин графа

Я искал сеть и не мог найти никакого объяснения алгоритма DFS для поиска всех вершин артикуляции графа. Нет даже вики-страницы.

из чтения вокруг, я должен знать основные факты отсюда. PDF

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

но я не поймите, как найти эту переменную down & up на каждом узле во время выполнения DFS. Что именно делает эта переменная?

пожалуйста, объясните алгоритм.

спасибо.

3 ответов


Поиск вершин артикуляции-это приложение DFS.

в двух словах

  1. применить DFS на графике. Получить дерево DFS.
  2. узел, который посещается ранее, является "родителем" тех узлов, которые достигаются им и посещаются позже.
  3. если у любого дочернего узла нет пути к любому из предков его родителя, это означает, что удаление этого узла сделает этот дочерний элемент непересекающимся из графика.
  4. есть исключение: корень дерева. Если у него больше одного ребенка, то это точка артикуляции, иначе нет.

пункт 3 по сути означает, что этот узел является точкой сочленения.

теперь для ребенка этот путь к предкам узла будет проходить через задний край от него или от любого из его потомков.

все это прекрасно объясняется в этом PDF.


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

на самом деле легко разработать алгоритм грубой силы для точек сочленения. Просто выньте вершину и запустите BFS или DFS на графике. Если он остается подключенным, то вершина не является точкой сочленения, иначе это. Это будет работать в O(V(E+V)) = O(EV) времени. Задача состоит в том, как это сделать в линейное время (т. е. O(E+V)).

точки артикуляции соединяют два (или более) подграфа. Это означает, что нет ребер от одного подграфа к другому. Поэтому представьте, что вы находитесь в одном из этих подграфов и посещаете его узел. Когда вы посещаете узел, вы отмечаете его, а затем переходите к следующему неотмеченному узлу, используя некоторое доступное ребро. Пока вы это делаете, откуда вы знаете, что находитесь внутри все того же подграфа? Понимание здесь заключается в том, что если вы находитесь в том же подграфе, вы в конечном итоге увидите помеченный узел через ребро при посещении неповрежденного узла. Это называется задним краем и указывает, что у вас есть цикл. Как только вы найдете задний край, вы можете быть уверены, что все узлы через этот помеченный узел к тому, который вы посещаете прямо сейчас, являются частью одного и того же подграфа, и между ними нет точек сочленения. Если вы не видели задних кромок, то все узлы, которые вы посетили до сих пор, являются точками артикуляции.

нам нужен алгоритм, который посещает вершины и отмечает все точки между целью задних кромок как в настоящее время-быть посещаемым узлы, как в том же подграфе. Очевидно, что внутри подграфов могут быть подграфы, поэтому нам нужно выбрать самый большой подграф, который у нас есть. Эти подграфы называются BI-компоненты. Мы можем реализовать этот алгоритм, назначив каждому bi-компоненту идентификатор, который инициализируется как просто количество вершин, которые мы посетили до сих пор. Позже, когда мы найдем задние края, мы можем сбросить BI-compinent ID до самого низа мы пока не добрались.

мы, очевидно, нужно два прохода. В первом проходе мы хотим выяснить, какую вершину мы можем видеть из каждой вершины через задние ребра, если таковые имеются. Во втором проходе мы хотим посетить вершины в противоположном направлении и собрать минимальный BI-component ID (т. е. самый ранний предок, доступный от любых потомков). DFS, естественно, подходит здесь. В DFS мы сначала спускаемся, а затем возвращаемся, поэтому оба вышеуказанных прохода можно сделать в одном DFS поперечный.

Теперь без дальнейших церемоний, вот псевдокод:

time = 0
visited[i] = false for all i
GetArticulationPoints(u)
    visited[u] = true
    u.st = time++
    u.low = v.st    //keeps track of highest ancestor reachable from any descendants
    dfsChild = 0    //needed because if no child then removing this node doesn't decompose graph
    for each ni in adj[i]
        if not visited[ni]
            GetArticulationPoints(ni)
            ++dfsChild
            parents[ni] = u
            u.low = Min(u.low, ni.low)  //while coming back up, get the lowest reachable ancestor from descendants
        else if ni <> parent[u] //while going down, note down the back edges
            u.low = Min(u.low, ni.st)

    //For dfs root node, we can't mark it as articulation point because 
    //disconnecting it may not decompose graph. So we have extra check just for root node.
    if (u.low = u.st and dfsChild > 0 and parent[u] != null) or (parent[u] = null and dfsChild > 1)
        Output u as articulation point
        Output edges of u with v.low >= u.low as bridges
    output u.low as bicomponent ID

если low потомка u больше dfsnum of u, потом u считается точкой артикуляции.

int adjMatrix[256][256];
int low[256], num=0, dfsnum[256];

void cutvertex(int u){
    low[u]=dfsnum[u]=num++;
    for (int v = 0; v < 256; ++v)
    {
        if(adjMatrix[u][v] && dfsnum[v]==-1)
        {
            cutvertex(v);
            if(low[v]>dfsnum[u])    
                cout<<"Cut Vertex: "<<u<<"\n";
            low[u]=min(low[u], low[v]);
        }
        else{
            low[u]=min(low[u], dfsnum[v]);
        }
    }
}