|
|
Строка 1: |
Строка 1: |
− | {| class="wikitable" align="center" style="color: red; background-color: black; font-size: 56px; width: 800px;"
| |
− | |+
| |
− | |-align="center"
| |
− | |'''НЕТ ВОЙНЕ'''
| |
− | |-style="font-size: 16px;"
| |
− | |
| |
− | 24 февраля 2022 года российское руководство во главе с Владимиром Путиным развязало агрессивную войну против Украины. В глазах всего мира это военное преступление совершено от лица всей страны, всех россиян.
| |
− |
| |
− | Будучи гражданами Российской Федерации, мы против своей воли оказались ответственными за нарушение международного права, военное вторжение и массовую гибель людей. Чудовищность совершенного преступления не оставляет возможности промолчать или ограничиться пассивным несогласием.
| |
− |
| |
− | Мы убеждены в абсолютной ценности человеческой жизни, в незыблемости прав и свобод личности. Режим Путина — угроза этим ценностям. Наша задача — обьединить все силы для сопротивления ей.
| |
− |
| |
− | Эту войну начали не россияне, а обезумевший диктатор. И наш гражданский долг — сделать всё, чтобы её остановить.
| |
− |
| |
− | ''Антивоенный комитет России''
| |
− | |-style="font-size: 16px;"
| |
− | |Распространяйте правду о текущих событиях, оберегайте от пропаганды своих друзей и близких. Изменение общественного восприятия войны - ключ к её завершению.
| |
− | |-style="font-size: 16px;"
| |
− | |[https://meduza.io/ meduza.io], [https://www.youtube.com/c/popularpolitics/videos Популярная политика], [https://novayagazeta.ru/ Новая газета], [https://zona.media/ zona.media], [https://www.youtube.com/c/MackNack/videos Майкл Наки].
| |
− | |}
| |
− |
| |
| == Алгоритм == | | == Алгоритм == |
| === Описание алгоритма === | | === Описание алгоритма === |
Текущая версия на 19:20, 4 сентября 2022
Алгоритм
Описание алгоритма
Алгоритм находит Эйлеров цикл как в ориентированном, так и в неориентированном графе. Перед запуском алгоритма необходимо проверить граф на эйлеровость. Чтобы построить Эйлеров путь, нужно запустить алгоритм из вершины с нечетной степенью.
Алгоритм напоминает поиск в глубину. Главное отличие состоит в том, что пройденными помечаются не вершины, а ребра графа. Начиная со стартовой вершины [math]v[/math] строим путь, добавляя на каждом шаге не пройденное еще ребро, смежное с текущей вершиной. Вершины пути накапливаются в стеке [math]S[/math]. Когда наступает такой момент, что для текущей вершины [math]w[/math] все инцидентные ей ребра уже пройдены, записываем вершины из [math]S[/math] в ответ, пока не встретим вершину, которой инцидентны не пройденные еще ребра. Далее продолжаем обход по не посещенным ребрам.
Псевдокод
Код проверки графа на эйлеровость:
boolean checkForEulerPath():
int OddVertex [math]= 0[/math]
for [math]v : v[/math] [math]\in[/math] [math]V[/math]
if [math]\operatorname{deg}[/math]([math]v[/math]) mod [math]2 == 1[/math]
OddVertex++
if OddVertex [math] \gt 2 [/math]// если количество вершин с нечетной степенью больше двух, то граф не является эйлеровым
return false
boolean visited([math]|V|[/math], false) // массив инициализируется значениями false
for [math]v : v[/math] [math]\in[/math] [math]V[/math]
if [math]\operatorname{deg}[/math]([math]v[/math]) [math] \gt 0[/math]
dfs([math]v[/math], visited)
break
for [math]v : v[/math] [math]\in[/math] [math]V[/math]
if [math]\operatorname{deg}[/math]([math]v[/math]) [math] \gt 0[/math] and not visited[[math]v[/math]] // если количество компонент связности, содержащие ребра, больше одной,
return false // то граф не является эйлеровым
return true // граф является эйлеровым
Код построения эйлерова пути:
function findEulerPath([math]v[/math]): // если граф является полуэйлеровым, то алгоритм следует запускать из вершины нечетной степени
for [math]u : u \in V[/math]
if [math]\operatorname{deg}[/math]([math]u[/math]) mod [math]2 == 1[/math]
[math]v = u[/math]
break
[math]S[/math].push([math]v[/math]) // [math]S[/math] — стек
while not [math]S[/math].empty()
[math]w = [/math] [math]S[/math].top()
found_edge = False
for [math]u : u \in V[/math]
if ([math]w, u[/math]) [math]\in E[/math] // нашли ребро, по которому ещё не прошли
[math]S[/math].push([math]u[/math]) // добавили новую вершину в стек
[math]E[/math].remove([math]w, u[/math])
found_edge = True
break
if not found_edge
[math]S[/math].pop() // не нашлось инцидентных вершине [math]w[/math] рёбер, по которым ещё не прошли
print([math]w[/math])
Доказательство корректности
Лемма: |
Данный алгоритм проходит по каждому ребру, причем ровно один раз. |
Доказательство: |
[math]\triangleright[/math] |
Допустим, что в момент окончания работы алгоритма имеются еще не пройденные ребра. Поскольку граф связен, должно существовать хотя бы одно не пройденное ребро, инцидентное посещенной вершине. Но тогда эта вершина не могла быть удалена из стека [math]S[/math], и он не мог стать пустым. Значит алгоритм пройдёт по всем рёбрам хотя бы один раз.
Но так как после прохода по ребру оно удаляется, то пройти по нему дважды алгоритм не может. |
[math]\triangleleft[/math] |
Вершина [math]v[/math], с которой начат обход графа, будет последней помещена в путь [math]P[/math]. Так как изначально стек пуст, и вершина [math]v[/math] входит в стек первой, то после прохода по инцидентным ребрам, алгоритм возвращается к данной вершине, выводит ее и опустошает стек, затем выполнение программы завершается.
Лемма: |
Напечатанный путь [math]P[/math] — корректный маршрут в графе, в котором каждые две соседние вершины [math]u_i[/math] и [math]u_{i+1}[/math] будут образовывать ребро [math](u_i, u_{i+1}) \in E[/math]. |
Доказательство: |
[math]\triangleright[/math] |
Будем говорить, что ребро [math](w,u)[/math] представлено в [math]S[/math] или [math]P[/math], если в какой-то момент работы алгоритма вершины [math]w[/math] и [math]u[/math] находятся рядом. Каждое ребро графа представлено в [math]S[/math]. Рассмотрим случай, когда из [math]S[/math] в [math]P[/math] перемещена вершина [math]u[/math], а следующей в [math]S[/math] лежит [math]w[/math]. Возможны 2 варианта:
- На следующем шаге для вершины [math]w[/math] не найдётся инцидентного ребра, тогда [math]w[/math] переместят в [math]P[/math], и ребро [math](w,u)[/math] будет представлено в [math]P[/math].
- Иначе будет пройдена некоторая последовательность ребер [math]{u_1, u_2, ..., u_k}[/math], начинающаяся в вершине [math]w[/math] и проходящая по ребру [math](w, u_1)[/math]. Докажем, что данный проход [math]{u_1, u_2, ..., u_k}[/math] закончится в вершине [math]w[/math]:
- Ребро [math](u_{k-1}, u_k)[/math] не может быть инцидентно вершинам [math]u_1, \dots , u_{k-2}[/math], иначе степень вершины [math]u_k[/math] окажется нечетной.
- Предположим, что [math](u_{k-1}, u_k)[/math] инцидентно вершине, пройденной при обходе графа из вершины [math]u[/math]. Но это неверно, так как тогда бы данные вершины пройдены ранее.
Из этого следует, что мы закончим обход в вершине [math]w[/math]. Следовательно, данная вершина первой поместится в [math]P[/math] вслед за [math]u[/math], и ребро [math](w, u)[/math] будет представлено в [math]P[/math]. |
[math]\triangleleft[/math] |
Теорема: |
Данный алгоритм находит корректный эйлеров путь. |
Доказательство: |
[math]\triangleright[/math] |
Из предыдущих лемм следует, что [math]P[/math] — искомый эйлеров путь и алгоритм работает корректно. |
[math]\triangleleft[/math] |
Рекурсивная реализация
function findEulerPath([math]v[/math] : Vertex):
for [math](v,u)[/math] [math]\in[/math] [math]E[/math]
remove [math](v, u)[/math]
findEulerPath([math]u[/math])
print([math]v[/math])
Время работы
Если реализовать поиск ребер инцидентных вершине и удаление ребер за [math]O(1)[/math], то алгоритм будет работать за [math]O(E)[/math].
Чтобы реализовать поиск за [math]O(1)[/math], для хранения графа следует использовать списки смежных вершин; для удаления достаточно добавить всем ребрам свойство [math]\mathtt{deleted}[/math] бинарного типа.
Рекурсивная реализация за [math]O(E)[/math]
Заведём 2 массива: [math]vis[/math] и [math]first[/math]
[math]vis[i] (bool)[/math] - посещено ли ребро с индексом [math]i[/math] [math](i \in 0..(E-1))[/math]
(массив нужен, чтобы за [math]O(1)[/math] проверять, доступно ребро или нет)
[math]first[u] (int)[/math] - индекс первой вершины [math]v[/math] в списке смежных вершин, такой что ребро [math](u,v)[/math] не посещено [math](u \in 0..(V-1))[/math]
(массив нужен, чтобы в среднем за [math]O(1)[/math] находить доступное ребро)
Изначально оба массива заполнены нулями.
Граф будем хранить в виде списков смежных вершин. Для каждой вершины [math]u[/math] построим список [math]g[u][/math] из пар вида [math](i,v)[/math]
[math]i[/math] - индекс ребра [math](u,v)[/math]
[math]v[/math] - номер смежной вершины
После ввода графа нужно запустить [math]euler(0)[/math] или от любой другой вершины.
function euler([math]u[/math]):
while (first[[math]u[/math]] < g[[math]u[/math]].size()): //если first[[math]u[/math]] = g[[math]u[/math]].size, рёбра во все смежные вершины уже посещены
[math]i[/math],[math]v[/math] = g[[math]u[/math]][first[[math]u[/math]]]
first[[math]u[/math]] += 1
if (!vis[[math]i[/math]]):
vis[[math]i[/math]] = true
euler([math]v[/math])
print([math]v[/math])
См. также
Источники информации