Использование обхода в глубину для поиска цикла — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Форматирование псевдокода, замена тире, источники информации)
(Добавлен случай неориентированности)
Строка 1: Строка 1:
Пусть дан [[ориентированный граф|ориентированный граф]] без петель и кратных рёбер. Требуется проверить наличие [[Основные определения теории графов|цикла]] в этом графе.
+
Пусть дан без петель и кратных рёбер. Требуется проверить наличие [[Основные определения теории графов|цикла]] в этом графе.
  
 
Решим эту задачу с помощью [[Обход в глубину, цвета вершин|поиска в глубину]] за <tex>O(M)</tex>.
 
Решим эту задачу с помощью [[Обход в глубину, цвета вершин|поиска в глубину]] за <tex>O(M)</tex>.
Строка 5: Строка 5:
 
== Алгоритм ==
 
== Алгоритм ==
  
Произведём серию поисков в глубину в графе. Т.е. из каждой вершины, в которую мы ещё ни разу не приходили, запустим поиск в глубину, который при входе в вершину будет красить её в серый цвет, а при выходе {{---}} в чёрный. И если поиск в глубину пытается пойти в серую вершину, то это означает, что мы нашли цикл.
+
Пусть дан <b>ориентированный граф</b>. Произведём серию поисков в глубину в графе. Т.е. из каждой вершины, в которую мы ещё ни разу не приходили, запустим поиск в глубину, который при входе в вершину будет красить её в серый цвет, а при выходе {{---}} в чёрный. И если поиск в глубину пытается пойти в серую вершину, то это означает, что мы нашли цикл.
  
Сам цикл можно восстановить проходом по массиву предков.
+
В случае <b>неориентированного графа</b> любое ребро представляется как два ребра {{---}} прямое и обратное. Тогда мы посчитаем, что эти два ребра составляют цикл, что неверно. Чтобы избежать этого, будем передавать еще один параметр поиска в глубину {{---}} вершину, из которой мы пришли. Теперь мы считаем, что нашли цикл, если вершина, в которую мы хотим пойти серая и не является вершиной из которой мы пришли. 
 +
 
 +
Для восстановления самого цикла достаточно при запуске поиска в глубину из очередной вершины добавлять эту вершину в стек. Когда поиск в глубину нашел вершину, которая лежит на цикле, будем последовательно вынимать вершины из стека, пока не встретим найденную еще раз. Все вынутые вершины будут лежать на искомом цикле.
 
[[Файл: Dfs_cycle.png|thumb|200px|right| Момент нахождения цикла: синие ребра {{---}} уже пройденные, красное ребро ведет в серую, уже пройденную, вершину.]]
 
[[Файл: Dfs_cycle.png|thumb|200px|right| Момент нахождения цикла: синие ребра {{---}} уже пройденные, красное ребро ведет в серую, уже пройденную, вершину.]]
  
Строка 16: Строка 18:
 
Докажем, что если в графе <tex>G</tex> существует цикл, то <tex>dfs(G)</tex> его всегда найдет. Пусть <tex> v </tex> {{---}} первая вершина принадлежащая циклу, рассмотренная поиском в глубину. Тогда существует вершина <tex> u </tex>, принадлежащая циклу и имеющая ребро в вершину <tex> v </tex>. Так как из вершины <tex> v </tex> в вершину <tex> u </tex> существует белый путь (они лежат на одном цикле), то по [[Лемма о белых путях|лемме о белых путях]] во время выполнения процедуры поиска в глубину от вершины <tex> u </tex>, вершина <tex> v </tex> будет серой. Так как из <tex> u </tex> есть ребро в <tex> v </tex>, то это ребро в серую вершину. Следовательно <tex>dfs(G)</tex> нашел цикл.
 
Докажем, что если в графе <tex>G</tex> существует цикл, то <tex>dfs(G)</tex> его всегда найдет. Пусть <tex> v </tex> {{---}} первая вершина принадлежащая циклу, рассмотренная поиском в глубину. Тогда существует вершина <tex> u </tex>, принадлежащая циклу и имеющая ребро в вершину <tex> v </tex>. Так как из вершины <tex> v </tex> в вершину <tex> u </tex> существует белый путь (они лежат на одном цикле), то по [[Лемма о белых путях|лемме о белых путях]] во время выполнения процедуры поиска в глубину от вершины <tex> u </tex>, вершина <tex> v </tex> будет серой. Так как из <tex> u </tex> есть ребро в <tex> v </tex>, то это ребро в серую вершину. Следовательно <tex>dfs(G)</tex> нашел цикл.
  
== Реализация ==
+
== Реализация для случая ориентированного графа ==
  
 
  '''int''' graph[][];
 
  '''int''' graph[][];
  '''int''' color[] <tex> \leftarrow </tex> white;
+
  '''int''' color[] <tex> \leftarrow </tex> white; <font color=darkgreen> // Массив цветов, изначально все вершины белые </font>
  
  '''func''' dfs(u: '''int'''):  
+
  '''func''' dfs(u: '''int'''):         <font color=darkgreen> // u {{---}} вершина, в которой мы сейчас находимся </font>
    color[u] = grey;            <font color=darkgreen> // красит вершину u в серый цвет </font>
+
    color[u] = grey;             
 
     '''for''' (v : uv {{---}} edge)
 
     '''for''' (v : uv {{---}} edge)
 
         if (color[v] == white)
 
         if (color[v] == white)
Строка 28: Строка 30:
 
         if (color[v] == grey)
 
         if (color[v] == grey)
 
             print();            <font color=darkgreen> // вывод ответа </font>   
 
             print();            <font color=darkgreen> // вывод ответа </font>   
     color[u] = black;       <font color=darkgreen> // красит вершину u в черный цвет </font>
+
     color[u] = black;        
 +
 
 +
== Реализация для случая неориентированного графа ==
 +
 
 +
'''int''' graph[][];
 +
'''int''' color[] <tex> \leftarrow </tex> white; <font color=darkgreen> // Массив цветов, изначально все вершины белые </font>
  
 +
'''func''' dfs(u, par: '''int'''): <font color=darkgreen> // u {{---}} вершина, в которой мы сейчас находимся; par {{---}} вершина, из которой мы пришли в u </font>
 +
    color[u] = grey;           
 +
    '''for''' (v : uv {{---}} edge)
 +
        if (color[v] == white)
 +
            dfs(v, u);
 +
        if (color[v] == grey '''and''' v <tex>\neq</tex> par)
 +
            print();            <font color=darkgreen> // вывод ответа </font> 
 +
    color[u] = black;       
 
== Источники информации ==
 
== Источники информации ==
 
* [http://e-maxx.ru/algo/finding_cycle MAXimal :: algo {{---}} «Проверка графа на ацикличность и нахождение цикла»]
 
* [http://e-maxx.ru/algo/finding_cycle MAXimal :: algo {{---}} «Проверка графа на ацикличность и нахождение цикла»]

Версия 12:14, 23 декабря 2014

Пусть дан без петель и кратных рёбер. Требуется проверить наличие цикла в этом графе.

Решим эту задачу с помощью поиска в глубину за [math]O(M)[/math].

Алгоритм

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

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

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

Момент нахождения цикла: синие ребра — уже пройденные, красное ребро ведет в серую, уже пройденную, вершину.

Доказательство

Пусть дан граф [math]G[/math]. Запустим [math]dfs(G)[/math]. Рассмотрим выполнение процедуры поиска в глубину от некоторой вершины [math] v [/math]. Так как все серые вершины лежат в стеке рекурсии, то для них вершина [math] v [/math] достижима, так как между соседними вершинами в стеке есть ребро. Тогда если из рассматриваемой вершины [math] v [/math] существует ребро в серую вершину [math] u [/math], то это значит, что из вершины [math] u [/math] существует путь в [math] v [/math] и из вершины [math] v [/math] существует путь в [math] u [/math] состоящий из одного ребра. И так как оба эти пути не пересекаются, то цикл существует.

Докажем, что если в графе [math]G[/math] существует цикл, то [math]dfs(G)[/math] его всегда найдет. Пусть [math] v [/math] — первая вершина принадлежащая циклу, рассмотренная поиском в глубину. Тогда существует вершина [math] u [/math], принадлежащая циклу и имеющая ребро в вершину [math] v [/math]. Так как из вершины [math] v [/math] в вершину [math] u [/math] существует белый путь (они лежат на одном цикле), то по лемме о белых путях во время выполнения процедуры поиска в глубину от вершины [math] u [/math], вершина [math] v [/math] будет серой. Так как из [math] u [/math] есть ребро в [math] v [/math], то это ребро в серую вершину. Следовательно [math]dfs(G)[/math] нашел цикл.

Реализация для случая ориентированного графа

int graph[][];
int color[] [math] \leftarrow [/math] white;  // Массив цветов, изначально все вершины белые 
func dfs(u: int):           // u — вершина, в которой мы сейчас находимся 
    color[u] = grey;              
    for (v : uv — edge)
        if (color[v] == white)
            dfs(v);
        if (color[v] == grey)
            print();              // вывод ответа    
    color[u] = black;         

Реализация для случая неориентированного графа

int graph[][];
int color[] [math] \leftarrow [/math] white;  // Массив цветов, изначально все вершины белые 
func dfs(u, par: int):  // u — вершина, в которой мы сейчас находимся; par — вершина, из которой мы пришли в u 
    color[u] = grey;             
    for (v : uv — edge)
        if (color[v] == white)
            dfs(v, u);
        if (color[v] == grey and v [math]\neq[/math] par)
            print();              // вывод ответа    
    color[u] = black;         

Источники информации