Нерекурсивный поиск по глубине в первую очередь на графиках для Delphi - PullRequest
4 голосов
/ 20 сентября 2010

Я ищу нерекурсивный алгоритм первого поиска по глубине на графах в Паскале (Delphi).

Мне нужна DFS для вычисления сильно или двухсвязных компонентов больших графов.В настоящее время я использую рекурсивный вариант алгоритма: http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm

Проблема состоит в том, что для такого алгоритма я должен определить большой объем памяти, который будет использоваться для стека, и это создает более поздние проблемы в Windows 7,где диалоги открытия и сохранения не работают из-за нескольких сгенерированных потоков ....

Итак, еще раз: я не вижу, как переписать алгоритм Tarjan DFS для работы без рекурсии.Есть ли у вас какие-либо предложения - или указать на нерекурсивный алгоритм для поиска в глубине на графиках?

Спасибо.

Ответы [ 3 ]

4 голосов
/ 23 сентября 2010

Алгоритм, описанный в Википедии, выглядит довольно легко сделать нерекурсивным с явным стеком. Начиная с этого (включен сюда для справки, в случае изменения Википедии):

Input: Graph G = (V, E)

index = 0                                         // DFS node number counter 
S = empty                                         // An empty stack of node
for all v in V do
  if (v.index is undefined)                       // Start a DFS at each node
    tarjan(v)                                     // we haven't visited yet

procedure tarjan(v)
  v.index = index                                 // Set the depth index for v
  v.lowlink = index                               // SHOULD BE v.lowlink = MAX_INT?
  index = index + 1
  S.push(v)                                       // Push v on the stack
  for all (v, v') in E do                         // Consider successors of v
    if (v'.index is undefined)                    // Was successor v' visited?
        tarjan(v')                                // Recurse
        v.lowlink = min(v.lowlink, v'.lowlink)
    else if (v' is in S)                          // Was successor v' in stack S? 
        v.lowlink = min(v.lowlink, v'.index )     // v' is in stack but it isn't in the dfs tree
  if (v.lowlink == v.index)                       // Is v the root of an SCC?
    print "SCC:"
    repeat
      v' = S.pop                                  
      print v'
    until (v' == v)

Шаг 1: Удалите циклы, содержащие рекурсию, добавление меток и переходов. Это необходимо для того, чтобы сделать переменные цикла явными, сохраняемыми и восстанавливаемыми (необходимо во время симуляции рекурсии со стеками). Метка должна быть добавлена ​​после возврата tarjan(), поскольку мы сразу к ней перейдем.

procedure tarjan(v)
  v.index = index                                 // Set the depth index for v
  v.lowlink = index                               // SHOULD BE v.lowlink = MAX_INT?
  index = index + 1
  S.push(v)                                       // Push v on the stack
  succ = all (v, v') in E      // Consider successors of v
  succIndex = 0                // presume succ is 0-based
loop_top:
  if succIndex >= Length(succ) goto skip_loop
  v' = succ[succIndex]
  if (v'.index is undefined)                    // Was successor v' visited?
      tarjan(v')                                // Recurse
recursion_returned:
      v.lowlink = min(v.lowlink, v'.lowlink)
  else if (v' is in S)                          // Was successor v' in stack S? 
      v.lowlink = min(v.lowlink, v'.index )     // v' is in stack but it isn't in the dfs tree
  succIndex = succIndex + 1
  goto loop_top
skip_loop:
  if (v.lowlink == v.index)                       // Is v the root of an SCC?
    print "SCC:"
    repeat
      v' = S.pop                                  
      print v'
    until (v' == v)

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

Стек:

T = empty // T will be our stack, storing (v, v', succ, succIndex, state)

state - это перечисление (TopState, ReturnedState), кодирующее местоположение в процедуре. Вот процедура, переписанная для использования этого стека и состояния, а не рекурсии:

procedure tarjan(v)
  while (T is not empty) do
    (v, v', succ, succIndex, state) = T.pop
    case state of
      TopState: goto top
      ReturnedState: goto recursion_returned
    end case
top:
    v.index = index                                 // Set the depth index for v
    v.lowlink = index                               // SHOULD BE v.lowlink = MAX_INT?
    index = index + 1
    S.push(v)                                       // Push v on the stack
    succ = all (v, v') in E      // Consider successors of v
    succIndex = 0                // presume succ is 0-based
loop_top:
    if succIndex >= Length(succ) goto skip_loop
    v' = succ[succIndex]
    if (v'.index is undefined)                    // Was successor v' visited?
      // instead of recursing, set up state for return and top and iterate
      T.push(v, v', succ, succIndex, ReturnedState) // this is where we return to
      T.push(v', empty, empty, empty, TopState) // but this is where we go first
      continue // continue the while loop at top
recursion_returned:
      v.lowlink = min(v.lowlink, v'.lowlink)
    else if (v' is in S)                          // Was successor v' in stack S? 
      v.lowlink = min(v.lowlink, v'.index )     // v' is in stack but it isn't in the dfs tree
    succIndex = succIndex + 1
    goto loop_top
skip_loop:
    if (v.lowlink == v.index)                       // Is v the root of an SCC?
    print "SCC:"
    repeat
      v' = S.pop                                  
      print v'
    until (v' == v)

Шаг 3: Наконец, нам нужно убедиться, что условия входа верны для кода верхнего уровня, который вызывает tarjan. Это легко сделать с помощью начального нажатия:

procedure tarjan(v)
  T.push(v, empty, empty, empty, TopState)
  while (T is not empty) do
    (v, v', succ, succIndex, state) = T.pop
    case state of
      TopState: goto top
      ReturnedState: goto recursion_returned
    end case
top:
    v.index = index                                 // Set the depth index for v
    v.lowlink = index                               // SHOULD BE v.lowlink = MAX_INT?
    index = index + 1
    S.push(v)                                       // Push v on the stack
    succ = all (v, v') in E      // Consider successors of v
    succIndex = 0                // presume succ is 0-based
loop_top:
    if succIndex >= Length(succ) goto skip_loop
    v' = succ[succIndex]
    if (v'.index is undefined)                    // Was successor v' visited?
      // instead of recursing, set up state for return and top and iterate
      T.push(v, v', succ, succIndex, ReturnedState) // this is where we return to
      T.push(v', empty, empty, empty, TopState) // but this is where we go first
      continue // continue the while loop at top
recursion_returned:
      v.lowlink = min(v.lowlink, v'.lowlink)
    else if (v' is in S)                          // Was successor v' in stack S? 
      v.lowlink = min(v.lowlink, v'.index )     // v' is in stack but it isn't in the dfs tree
    succIndex = succIndex + 1
    goto loop_top
skip_loop:
    if (v.lowlink == v.index)                       // Is v the root of an SCC?
    print "SCC:"
    repeat
      v' = S.pop                                  
      print v'
    until (v' == v)

Это также можно сделать прыжком, сразу прыгнув на top. Код может быть дополнительно очищен, возможно, преобразован для использования цикла while или repeat для устранения некоторых ошибок и т. Д., Но вышеприведенное должно быть по крайней мере функционально эквивалентным, исключая явную рекурсию.

1 голос
/ 05 апреля 2015

Устранить рекурсию в алгоритме Тарьяна сложно. Конечно, это требует сложного кода. Алгоритм Косараю является альтернативным решением. Я считаю, что устранение рекурсии в алгоритме Косараджу намного проще.

Вы можете попробовать себя с помощью алгоритма Косараджу, описанного в Википедии, или следовать следующим инструкциям.

1. Перечислите узлы в порядке DFS.

Пусть G1 - ориентированный граф, а List - пустой стек.

Для каждого узла v, не входящего в List, выполнить DFS, начиная с v. Каждый раз, когда DFS заканчивается в узле u, нажимайте u на List.

Для DFS без рекурсии создайте стек с именем st. Каждый элемент в st представляет команду.

  • Положительный элемент x означает «посетить узел x, если x не посещен».
    • Каждый раз, посещая узел x, помещайте -x в стек, затем вставляйте узлы y рядом с x.
  • Отрицательный элемент -x означает «закончить посещение х».
    • Каждый раз, заканчивая посещение x, нажимайте x на List.

Рассмотрим следующий код:

const int N = 100005;
bool Dfs[N];                // check if a node u is visited
vector<int> List;

void dfs(int U) {
    stack<int> st; st.push(U);
    while (st.size()) {
        int u=st.top(); st.pop();
        if (u<0) {
            List.push_back(-u);
        } else if (!Dfs[u]) {
            Dfs[u] = true;
            st.push(-u);
            // for each node v adjacent with u in G1
            for (int i=0; int v=a1[u][i]; i++)
            if (!Dfs[v]) st.push(v);
        }
    }
}

for (int i=1; i<=n; i++)
    if (!Dfs[i]) dfs(i);

Теперь List создано.

2. BFS согласно Списку

Пусть G2 будет графом транспонирования G1 (Обратное направление всех дуг в G1, чтобы получить граф транспонирования G2).

Пока List не пусто, откройте верхний узел v из List. Выполните BFS в G2, начиная с v, посещенные узлы сливаются в новый SCC. Удалите такие узлы как из G2, так и List.

.

Рассмотрим следующий код:

bool Bfs[N];
int Group[N]; // the result

void bfs(int U) {
    queue<int> qu; 
    qu.push(U); Bfs[U]=true;
    while (qu.size()) {
        int u=qu.front(); qu.pop();
        Group[u]=U;
        // for each node v adjacent with u in G2
        for (int i=0; int v=a2[u][i]; i++)
        if (!Bfs[v]) { qu.push(v); Bfs[v]=true; }
    }
}

for (int i=List.size()-1; i>=0; i--)
    if (!Bfs[List[i]]) bfs(List[i]);

Результат находится в массиве Group.

0 голосов
/ 20 сентября 2010

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

За исключением того, что ваш набор данных может быть настолько большим, что переполняет стек процесса.Если это так, я бы порекомендовал написать итерационную версию алгоритма, который использует предоставленный вами стек.Стек должен находиться в куче, а не в стеке.Вам нужно будет сохранить контекст поиска самостоятельно, а не позволять алгоритму это делать.

Этот алгоритм является рекурсивным алгоритмом.Период.Вам не нужно писать функцию, которая вызывает себя, но в конце вам все равно нужно будет отслеживать, где вы были, и порядок посещенных вами узлов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...