1632
правки
Изменения
м
== Время работы ==Если реализовать удаление ребер за Граф будем хранить в виде списков смежных вершин. Для каждой вершины <tex>u</tex> построим список <tex>g[u]</tex> из пар вида <tex>O(1i,v)</tex>, то алгоритм будет работать за <br><tex>i</tex> - индекс ребра <tex>O(Eu,v)</tex>.<br><tex>v</tex> - номер смежной вершины <br>
rollbackEdits.php mass rollback
== Алгоритм ===== Описание алгоритма ===Приведенный ниже псевдокод алгоритма Алгоритм находит [[Эйлеров цикл, Эйлеров путь, Эйлеровы графы, Эйлеровость орграфов|Эйлеров цикл]] как в [[Ориентированный граф|ориентированном графе]], так и в [[Основные определения теории графов#Неориентированные графы|неориентированном графе]]. Перед запуском алгоритма необходимо [[Эйлеров цикл, Эйлеров путь, Эйлеровы графы, Эйлеровость орграфов|проверить граф на эйлеровость]]. Чтобы построить [[Эйлеров цикл, Эйлеров путь, Эйлеровы графы, Эйлеровость орграфов|Эйлеров путь]], нужно запустить функцию алгоритм из вершины с нечетной степенью.<br>Алгоритм напоминает поиск в глубину. Главное отличие состоит в том, что пройденными помечаются не вершины, а ребра графа. Начиная со стартовой вершины <tex>v</tex> строим путь, добавляя на каждом шаге не пройденное еще ребро, смежное с текущей вершиной. Вершины пути накапливаются в [[Стек | стеке]] <tex>S</tex>. Когда наступает такой момент, что для текущей вершины <tex>w</tex> все инцидентные ей ребра уже пройдены, записываем вершины из <tex>S</tex> в ответ, пока не встретим вершину, которой инцидентны не пройденные еще ребра. Далее продолжаем обход по не посещенным ребрам.
=== Псевдокод ===<font size=32>'''Код проверки графа на эйлеровость:''' '''boolean''' checkForEulerPath(): '''int''' OddVertex <tex>= 0</tex> '''for''' <tex>v : v</tex> <tex>\in</tex> <tex>V</tex> '''if''' <tex>\operatorname{deg}</tex>(<tex>v</tex>) '''mod''' <tex>2 == 1</tex> OddVertex++ '''if''' OddVertex <tex> > 2 </tex><font color=darkgreen>// если количество вершин с нечетной степенью больше двух, то граф не является эйлеровым</font> '''return''' ''false'' '''boolean''' visited(<tex>|V|</tex>, ''false'') <font color=darkgreen>// массив инициализируется значениями ''false''</font> '''for''' <tex>v : v</tex> <tex>\in</tex> <tex>V</tex> '''if''' <tex>\operatorname{deg}</tex>(<tex>v</tex>) <tex> > 0</tex> dfs(<tex>v</tex>, visited) '''break''' '''for''' <tex>v : v</tex> <tex>\in</tex> <tex>V</tex> '''if''' <tex>\operatorname{deg}</tex>(<tex>v</tex>) <tex> > 0</tex> '''and''' '''not''' visited[<tex>v</tex>] <font color=darkgreen>// если количество компонент связности, содержащие ребра, больше одной,</font> '''return''' ''false'' <font color=darkgreen> // то граф не является эйлеровым</font> '''return''' ''true'' findPath<font color=darkgreen>// граф является эйлеровым</font> '''Код построения эйлерова пути:''' '''function''' findEulerPath(<tex>v</tex>): <font color=darkgreen> // если граф является полуэйлеровым, то алгоритм следует запускать из вершины нечетной степени </font> S.clear'''for''' <tex>u : u \in V</tex> '''if''' <tex>\operatorname{deg}</tex>(<tex>u</tex>)'''mod''' <tex>2 == 1</tex> <tex>v = u</tex> '''break''' <tex>S</tex>.addpush(<tex>v</tex>) <font color=darkgreen>// <tex>S</tex> {{---}} стек</font> '''while not stack''' <tex>S</tex>.isEmptyempty(): <tex>w := </tex> <tex>S</tex>.top() found_edge = '''False''' '''for''' <tex>u : u \in V</tex> '''if E contains ''' (<tex>w, u</tex>):<tex>\in E</tex> <font color=darkgreen> // нашли ребро, по которому ещё не прошли</font> <tex>S</tex>.addpush(<tex>u</tex>)<font color=darkgreen> // добавили новую вершину в стек</font> <tex>E</tex>.remove(<tex>w, u</tex>) else: found_edge = '''True''' '''break''' '''if''' '''not''' found_edge <tex>S</tex>.pop()<font color=darkgreen> // не нашлось инцидентных вершине <tex>w</tex> рёбер, по которым ещё не прошли</font> print (<tex>w</tex>)
</font>
=== Доказательство корректности ==={{Лемма|statement=Данный алгоритм проходит по каждому ребру, причем ровно один раз.Пусть |proof=Допустим, что в момент окончания работы алгоритма имеются еще не пройденные ребра. Поскольку граф связен, должно существовать хотя бы одно не пройденное ребро, инцидентное посещенной вершине. Но тогда эта вершина не могла быть удалена из стека <tex>PS</tex> - напечатанный путь, и он не мог стать пустым. Значит алгоритм пройдёт по всем рёбрам хотя бы один раз.Но так как после прохода по ребру оно удаляется, то пройти по нему дважды алгоритм не может. Заметим<br>}}Вершина <tex>v</tex>, с которой начат обход графа, что первой будет последней помещена в путь <tex>SP</tex> помещается . Так как изначально стек пуст, и вершина <tex>v</tex>входит в стек первой, то после прохода по инцидентным ребрам, алгоритм возвращается к данной вершине, выводит ее и она будет последней перемещена из опустошает стек, затем выполнение программы завершается.<br>{{Лемма|statement=Напечатанный путь <tex>SP</tex> {{---}} корректный маршрут в графе, в котором каждые две соседние вершины <tex>u_i</tex> и <tex>u_{i+1}</tex> будут образовывать ребро <tex>P(u_i, u_{i+1}) \in E</tex>. Следовательно|proof=Будем говорить, что ребро <tex>(w, она будет последней вершиной u)</tex> представлено в <tex>S</tex> или <tex>P</tex>, если в какой-то момент работы алгоритма вершины <tex>w</tex> и <tex>u</tex> находятся рядом. Далее, как было отмечено выше, первый раз, когда обнаружится, что все инцидентные активной вершине ребра пройдены, активной будет стартовая вершина Каждое ребро графа представлено в <tex>vS</tex>. ЗначитРассмотрим случай, эта вершина будет первой перемещена когда из <tex>S</tex> в <tex>P</tex> перемещена вершина <tex>u</tex>, а следующей в <tex>S</tex> лежит <tex>w</tex>. ИтакВозможны 2 варианта:*На следующем шаге для вершины <tex>w</tex> не найдётся инцидентного ребра, по окончании работы алгоритма тогда <tex>w</tex> переместят в начале <tex>P</tex>, и в конце последовательности вершинребро <tex>(w, содержащейся u)</tex> будет представлено в <tex>P</tex>.*Иначе будет пройдена некоторая последовательность ребер <tex>{u_1, u_2, ..., u_k}</tex>, находится вершина начинающаяся в вершине <tex>w</tex> и проходящая по ребру <tex>v(w, u_1)</tex>. Иначе говоряДокажем, что данный проход <tex>{u_1, если эта последовательность представляет маршрутu_2, то этот маршрут замкнут..., u_k}</tex> закончится в вершине <brtex>w</tex>:#Ребро <tex>(u_{k-1}, u_k)</tex> не может быть инцидентно вершинам <tex>u_1, \dots , u_{k-2}</tex>, иначе степень вершины <tex>u_k</tex>окажется нечетной.Покажем#Предположим, что маршрут замкнут.<brtex>(u_{k-1}, u_k)</tex> инцидентно вершине, пройденной при обходе графа из вершины <tex>u</tex>. Но это неверно, так как тогда бы данные вершины пройдены ранее.ОтметимИз этого следует, что мы закончим обход в конечном итоге каждое вершине <tex>w</tex>. Следовательно, данная вершина первой поместится в <tex>P</tex> вслед за <tex>u</tex>, и ребро <tex>(w, u)</tex> будет пройденопредставлено в <tex>P</tex>. Действительно, допустим}}{{Теорема|id=proof1|statement=Данный алгоритм находит корректный эйлеров путь.|proof=Из предыдущих лемм следует, что в момент окончания работы алгоритма имеются еще не пройденные ребра<tex>P</tex> {{---}} искомый эйлеров путь и алгоритм работает корректно. Поскольку граф связен}}=== Рекурсивная реализация ===<font size=2> '''function''' findEulerPath(<tex>v</tex> : Vertex): '''for''' <tex>(v, должно существовать хотя бы одно непройденное реброu)</tex> <tex>\in</tex> <tex>E</tex> remove <tex>(v, инцидентное посещенной u)</tex> findEulerPath(<tex>u</tex>) print(<tex>v</tex>)</font>=== Время работы ===Если реализовать поиск ребер инцидентных вершине. Но тогда эта вершина не могла быть удалена из и удаление ребер за <tex>SO(1)</tex>, и то алгоритм будет работать за <tex>SO(E)</tex> не мог стать пустым.<br>Чтобы реализовать поиск за <tex>O(1)</tex>, для хранения графа следует использовать списки смежных вершин; для удаления достаточно добавить всем ребрам свойство <tex> \Box mathtt{deleted}</tex>бинарного типа.
=== Рекурсивная реализация за <tex>O(E)</tex> === Заведём 2 массива: <tex>vis</tex> и <font size=3tex>first</tex> <br> findPath<tex>vis[i] (bool)</tex> - посещено ли ребро с индексом <tex>i</tex> <tex>(i \in 0..(vE-1)):</tex><br> for (vмассив нужен, чтобы за <tex>O(1)</tex> проверять, uдоступно ребро или нет) from E<br> remove <tex>first[u] (int)</tex> - индекс первой вершины <tex>v</tex> в списке смежных вершин, такой что ребро <tex>(u,v)</tex> не посещено <tex>(u\in 0..(V-1))</tex><br> findPath(uмассив нужен, чтобы в среднем за <tex>O(1)</tex> находить доступное ребро)<br> print vИзначально оба массива заполнены нулями. </fontbr>
После ввода графа нужно запустить <tex>euler(0)</tex> или от любой другой вершины. <br>
<font size=2>
'''function''' euler(<tex>u</tex>):
'''while''' (first[<tex>u</tex>] < g[<tex>u</tex>].size()): <font color=darkgreen> //если first[<tex>u</tex>] = g[<tex>u</tex>].size, рёбра во все смежные вершины уже посещены</font>
<tex>i</tex>,<tex>v</tex> = g[<tex>u</tex>][first[<tex>u</tex>]]
first[<tex>u</tex>] += 1
'''if''' (!vis[<tex>i</tex>]):<br> vis[<tex>i</tex>] = true<br> euler(<tex>v</tex>)<br> print(<tex>v</tex>)
</font> <br>
== См. также ==
* [[Гамильтоновы графы]]* [[Покрытие рёбер графа путями]]* [[Произвольно вычерчиваемые из заданной вершины графы]] == Источники информации ==* [http://ru.wikipedia.org/wiki/Эйлеров_цикл Википедия {{---}} Эйлеров цикл, Эйлеров путь, Эйлеровы графы, Эйлеровость орграфов]]
* [http://e-maxx.ru/algo/euler_path Статья про нахождение Эйлерова пути с реализацией на С++ на сайте e-maxx.ru]
* [http://ивтб.рф/exams/саод/36.htm Статья про нахождение Эйлерова пути с реализацией на Pascal на сайте ивтб.рф]
* [https://www.youtube.com/watch?v=ryw059C6oK8 Видео-лекция А.С.Станкевича про нахождение Эйлерова цикла с реализацией на C++ на сайте youtube.com]
[[Категория: Алгоритмы и структуры данных]]
[[Категория: Обходы графов]]
[[Категория: Эйлеровы графы]]