Алгоритм Левита — различия между версиями
(→Доказательство) |
м (rollbackEdits.php mass rollback) |
||
(не показана 21 промежуточная версия 4 участников) | |||
Строка 1: | Строка 1: | ||
− | '''Алгоритм Левита''' (Levit algorithm) находит расстояние от заданной вершины <tex>s</tex> до всех остальных. Позволяет работать с ребрами отрицательного веса при отсутствии отрицательных циклов. | + | '''Алгоритм Левита''' (англ. ''Levit's algorithm'') находит расстояние от заданной вершины <tex>s</tex> до всех остальных. Позволяет работать с ребрами отрицательного веса при отсутствии отрицательных циклов. |
== Алгоритм == | == Алгоритм == | ||
Строка 6: | Строка 6: | ||
Разделим вершины на три множества: | Разделим вершины на три множества: | ||
− | * <tex>M_0</tex> {{---}} вершины, расстояние до которых уже вычислено (возможно, не окончательно) | + | * <tex>M_0</tex> {{---}} вершины, расстояние до которых уже вычислено (возможно, не окончательно), |
− | * <tex>M_1</tex> {{---}} вершины, расстояние до которых вычисляется. Это множество в свою очередь делится на | + | * <tex>M_1</tex> {{---}} вершины, расстояние до которых вычисляется. Это множество в свою очередь делится на две [[Очередь|очереди]]: |
− | # <tex>M_1^{'}</tex> {{---}} основная очередь | + | # <tex>M_1^{'}</tex> {{---}} основная очередь, |
− | # <tex>M_1^{''}</tex> {{---}} срочная очередь | + | # <tex>M_1^{''}</tex> {{---}} срочная очередь; |
− | * <tex>M_2</tex> {{---}} вершины, расстояние до которых еще не вычислено | + | * <tex>M_2</tex> {{---}} вершины, расстояние до которых еще не вычислено. |
Изначально все вершины, кроме <tex>s</tex> помещаются в множество <tex>M_2</tex>. Вершина <tex>s</tex> помещается в множество <tex>M_1</tex> (в любую из очередей). | Изначально все вершины, кроме <tex>s</tex> помещаются в множество <tex>M_2</tex>. Вершина <tex>s</tex> помещается в множество <tex>M_1</tex> (в любую из очередей). | ||
− | + | '''Шаг алгоритма:''' выбирается вершина <tex>u</tex> из <tex>M_1</tex>. Если очередь <tex>M_1^{''}</tex> не пуста, то вершина берется из нее, иначе из <tex>M_1^{'}</tex>. Для каждого ребра <tex>uv \in E</tex> возможны три случая: | |
− | '''Шаг алгоритма:''' выбирается вершина <tex>u</tex> из <tex>M_1</tex>. Если очередь <tex>M_1^{''}</tex> не пуста, то вершина берется из нее, иначе из <tex>M_1^{'}</tex>. | + | * <tex>v \in M_2</tex>, то <tex>v</tex> переводится в конец очереди <tex>M_1^{'}</tex>. При этом <tex>d_v \gets d_u + w_{uv}</tex> (производится релаксация ребра <tex>uv</tex>), |
− | * | + | * <tex>v \in M_1</tex>, то происходит релаксация ребра <tex>uv</tex>, |
− | * | + | * <tex>v \in M_0</tex>. Если при этом <tex>d_v > d_u + w_{uv}</tex>, то происходит релаксация ребра <tex>uv</tex> и вершина <tex>v</tex> помещается в <tex>M_1^{''}</tex>; иначе ничего не делаем. |
− | * | ||
В конце шага помещаем вершину <tex>u</tex> в множество <tex>M_0</tex>. | В конце шага помещаем вершину <tex>u</tex> в множество <tex>M_0</tex>. | ||
− | |||
Алгоритм заканчивает работу, когда множество <tex>M_1</tex> становится пустым. | Алгоритм заканчивает работу, когда множество <tex>M_1</tex> становится пустым. | ||
Строка 26: | Строка 24: | ||
== Псевдокод == | == Псевдокод == | ||
− | + | Для хранения вершин используем следующие структуры данных: | |
− | + | * <tex>M_0</tex> {{---}} [[Хеш-таблица|хеш-таблица]], | |
− | <tex> | + | * <tex>M_1</tex> {{---}} основная и срочная [[Очередь|очереди]], |
− | + | * <tex>M_2</tex> {{---}} [[Хеш-таблица|хеш-таблица]]. | |
− | <tex>M_1^{ | + | |
− | '''for''' | + | '''for''' <tex>u : u \in V</tex> |
− | <tex>M_2</tex>.add(u) | + | <tex>d[u] = \infty</tex> |
− | + | <tex>d[s] = 0</tex> | |
− | '''while''' <tex>M_1^{'} \neq \varnothing</tex> '''and''' <tex>M_1^{''} \neq \varnothing</tex> | + | <tex>M_1^{'}</tex>.push(<tex>s</tex>) |
− | + | '''for''' <tex>u : u \neq s</tex> '''and''' <tex>u \in V</tex> | |
− | + | <tex>M_2</tex>.add(<tex>u</tex>) | |
− | + | '''while''' <tex>M_1^{'} \neq \varnothing</tex> '''and''' <tex>M_1^{''} \neq \varnothing</tex> | |
− | + | <tex>u=(M_1^{''} = \varnothing</tex> <tex>?</tex> <tex>M_1^{'}</tex>.pop() <tex>:</tex> <tex>M_1{''}</tex>.pop()<tex>)</tex> | |
− | + | '''for''' <tex>v : uv \in E</tex> | |
− | '''for''' | + | '''if''' <tex>v \in M_2</tex> |
− | '''if''' | + | <tex>M_1^{'}</tex>.push(<tex>v</tex>) |
− | <tex>M_1^{'}</tex>.push(v) | + | <tex>M_2</tex>.remove(<tex>v</tex>) |
− | + | <tex>d[v] =</tex> min(<tex>d[v], d[u] + w_{uv}</tex>) | |
− | '''if''' v <tex>\in M_1</tex> | + | '''else if''' <tex>v</tex> <tex>\in M_1</tex> |
− | + | <tex>d[v] =</tex> min(<tex>d[v], d[u] + w_{uv}</tex>) | |
− | '''if''' | + | '''else if''' <tex>v \in M_0</tex> '''and''' <tex>d[v] > d[u] + w_{uv}</tex> |
− | <tex>M_1^{''}</tex>.push(v) | + | <tex>M_1^{''}</tex>.push(<tex>v</tex>) |
− | + | <tex>M_0</tex>.remove(<tex>v</tex>) | |
− | + | <tex>d[v] = d[u] + w_{uv}</tex> | |
− | <tex>M_0</tex>.add(u) | + | <tex>M_0</tex>.add(<tex>u</tex>) |
== Доказательство == | == Доказательство == | ||
Строка 56: | Строка 54: | ||
{{Лемма | {{Лемма | ||
|statement= Алгоритм отработает за конечное время | |statement= Алгоритм отработает за конечное время | ||
− | |proof= Не теряя общности, будем считать, что граф связен. Тогда | + | |proof= Не теряя общности, будем считать, что граф связен. Тогда алгоритм завершит работу, когда в <tex>M_0</tex> окажутся все вершины. Так как в исходном графе нет отрицательных циклов, то для каждой вершины существует кратчайший путь. Тогда расстояние до каждой вершины может уменьшится только конечное число раз и, как следствие, вершина будет переведена из <tex>M_0</tex> в <tex>M_1</tex> тоже конечное число раз. С другой стороны, на каждом шаге текущая вершина гарантированно помещается в <tex>M_0</tex>. Тогда за конечное число шагов все вершины окажутся в <tex>M_0</tex>. |
}} | }} | ||
Строка 63: | Строка 61: | ||
|proof= Предположим обратное. Тогда рассмотрим 2 случая: | |proof= Предположим обратное. Тогда рассмотрим 2 случая: | ||
# Вершина <tex>u</tex> попала в <tex>M_0</tex> позже <tex>v</tex>. Тогда должна была произойти релаксация ребра <tex>uv</tex> и она была неуспешной. Значит, такого варианта не может быть | # Вершина <tex>u</tex> попала в <tex>M_0</tex> позже <tex>v</tex>. Тогда должна была произойти релаксация ребра <tex>uv</tex> и она была неуспешной. Значит, такого варианта не может быть | ||
− | # Вершина <tex>u</tex> попала в <tex> | + | # Вершина <tex>u</tex> попала в <tex>M_0</tex> раньше <tex>v</tex>. Заметим, что с момента последнего попадания <tex>u</tex> в <tex>M_0</tex> расстояние до нее не менялось (иначе, вершина была бы извлечена из <tex>M_0</tex>). Вес ребра <tex>uv</tex> тоже не меняется. Значит, и релаксация ребра <tex>uv</tex> ничего не даст |
Противоречие. | Противоречие. | ||
}} | }} | ||
+ | |||
+ | Из двух предыдущих лемм напрямую следует корректность алгоритма. | ||
== Сложность == | == Сложность == | ||
− | == Источники == | + | При неправильной реализации алгоритма, используя вместо очередей <tex>M_1{''}</tex> и <tex>M_1{'}</tex> [[Персистентный дек|дек]] и добавляя вершины из <tex>M_0</tex> в начало дека, алгоритм в худшем случае будет работать за экспоненциальное время, так делать не рекомендуется. |
− | * [http://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9B%D0%B5%D0%B2%D0%B8%D1%82%D0%B0 Алгоритм Левита | + | |
− | * [http://e-maxx.ru/algo/levit_algorithm | + | В плохих случаях алгоритм Левита работает за <tex>O(n^2m)</tex>. Рассмотрим полный граф <tex>K_n</tex> с <tex>n</tex> вершинами и такими <tex>m</tex> рёбрами, идущими в [[Лексикографический порядок|лексикографическом порядке]]: |
− | * И. В. Романовский, Дискретный анализ, ISBN 5-7940-0138-0; 2008 г., стр. | + | * для всех вершин <tex>1 < i < j \leqslant n</tex> вес ребра <tex>(i,j) = j - i - 1</tex>, т.е. количество вершин между <tex>i</tex> и <tex>j</tex>; <tex>w_{i,i+1}=0</tex>, |
+ | * ребро <tex>(1,n)</tex> веса <tex>0</tex>, | ||
+ | * для всех вершин <tex>1 < i < n</tex> вес ребра <tex>(1,i) = w_{1,i+1} + i - 1</tex>; от <tex>1</tex> до <tex>i</tex> вершины расстояние равно <tex>\sum\limits_{k=i-1}^{n-2}k</tex>. | ||
+ | Ясно, что кратчайший путь до каждой вершины равен <tex>0</tex>, но в плохом случае алгоритм при подсчёте вершины <tex>i</tex> будет пересчитывать все вершины до неё (кроме первой). На <tex>1</tex> шаге в очередь положат вершины от <tex>2</tex> до <tex>n</tex>, причём вершину <tex>1</tex> из <tex>M_0</tex> больше не достанут. На следующем шаге добавлений не произойдёт, так как вершины больше <tex>2</tex> уже в очереди. На <tex>3</tex> шаге алгоритм улучшит расстояние до вершины <tex>2</tex> на <tex>1</tex> (что видно из веса рёбер <tex>(1,2)</tex> и <tex>(1,3)</tex>, равных <tex>\sum\limits_{k=1}^{n-2}k</tex> и <tex>\sum\limits_{k=2}^{n-2}k</tex> соответственно), так что её добавят в <tex>M_1{''}</tex> и обработают на <tex>4</tex> шаге (релаксаций не происходит). На следующем шаге из обычной очереди достанут вершину <tex>4</tex>, расстояние до неё, равное <tex>\sum\limits_{k=3}^{n-2}k</tex>, на <tex>2</tex> меньше, чем расстояние до <tex>2</tex> и <tex>3</tex> вершин. Их добавят в срочную очередь, но так как <tex>w_{24}-1=w_{34}</tex>, то после подсчёта вершины <tex>3</tex> вершину <tex>2</tex> снова добавят в <tex>M_1{''}</tex>. Затем дойдёт очередь до вершины <tex>5</tex>, что вызовет релаксацию предыдущих вершин <tex>2,3,4</tex>, затем прорелаксируют вершины <tex>2,3</tex>, и после вершина <tex>2</tex>. Аналогично будут происходить релаксации всех вершин при обработке вершины <tex>i</tex> из очереди <tex>M_0</tex>. Таким образом, вершину <tex>i</tex> будут добавлять в срочную очередь <tex>n-i</tex> раз (добавление вершин из очереди <tex>M_2</tex> с номером больше <tex>i</tex>) <tex>+</tex> количество добавлений "старшей" вершины <tex>i+1</tex>. Количество добавлений вершины <tex>i</tex> составит <tex>1 + \sum\limits_{k=1}^{n-i}k</tex>, а сумма всех добавлений примерно составит <tex>O(nm)</tex>. При обработке каждой вершины приходится обходить <tex>n-1</tex> ребер, что даёт оценку <tex>O(n^2m)</tex>. Однако, на реальных графах алгоритм Левита работает быстрее, чем алгоритм [[Алгоритм Форда-Беллмана|Форда Беллмана]] и не многим уступает алгоритму [[Алгоритм Дейкстры|Дейкстры]]. | ||
+ | |||
+ | ==См. также== | ||
+ | * [[Алгоритм A*]] | ||
+ | * [[Алгоритм Дейкстры]] | ||
+ | * [[Алгоритм Джонсона]] | ||
+ | * [[Алгоритм Флойда]] | ||
+ | * [[Алгоритм Форда-Беллмана]] | ||
+ | * [[Обход в ширину]] | ||
+ | |||
+ | == Источники информации== | ||
+ | * [http://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9B%D0%B5%D0%B2%D0%B8%D1%82%D0%B0 Википедия — Алгоритм Левита] | ||
+ | * [http://e-maxx.ru/algo/levit_algorithm MAXimal :: algo :: Алгоритм Левита] | ||
+ | * И. В. Романовский, Дискретный анализ, ISBN 5-7940-0138-0; 2008 г., 4 издание, стр. 229-231. | ||
+ | |||
+ | [[Категория: Алгоритмы и структуры данных]] | ||
+ | [[Категория: Кратчайшие пути в графах]] |
Текущая версия на 19:35, 4 сентября 2022
Алгоритм Левита (англ. Levit's algorithm) находит расстояние от заданной вершины
до всех остальных. Позволяет работать с ребрами отрицательного веса при отсутствии отрицательных циклов.Алгоритм
Пусть
— текущая длина кратчайшего пути до вершины . Изначально, все элементы , кроме -го равны бесконечности; .Разделим вершины на три множества:
- — вершины, расстояние до которых уже вычислено (возможно, не окончательно),
- очереди: — вершины, расстояние до которых вычисляется. Это множество в свою очередь делится на две
- — основная очередь,
- — срочная очередь;
- — вершины, расстояние до которых еще не вычислено.
Изначально все вершины, кроме
помещаются в множество . Вершина помещается в множество (в любую из очередей).Шаг алгоритма: выбирается вершина
из . Если очередь не пуста, то вершина берется из нее, иначе из . Для каждого ребра возможны три случая:- , то переводится в конец очереди . При этом (производится релаксация ребра ),
- , то происходит релаксация ребра ,
- . Если при этом , то происходит релаксация ребра и вершина помещается в ; иначе ничего не делаем.
В конце шага помещаем вершину
в множество .Алгоритм заканчивает работу, когда множество
становится пустым.Псевдокод
Для хранения вершин используем следующие структуры данных:
- хеш-таблица, —
- очереди, — основная и срочная
- хеш-таблица. —
for.push( ) for and .add( ) while and .pop() .pop() for if .push( ) .remove( ) min( ) else if min( ) else if and .push( ) .remove( ) .add( )
Доказательство
Лемма: |
Алгоритм отработает за конечное время |
Доказательство: |
Не теряя общности, будем считать, что граф связен. Тогда алгоритм завершит работу, когда в | окажутся все вершины. Так как в исходном графе нет отрицательных циклов, то для каждой вершины существует кратчайший путь. Тогда расстояние до каждой вершины может уменьшится только конечное число раз и, как следствие, вершина будет переведена из в тоже конечное число раз. С другой стороны, на каждом шаге текущая вершина гарантированно помещается в . Тогда за конечное число шагов все вершины окажутся в .
Лемма: |
В конце работы алгоритма не найдется такое ребро , что его релаксация будет успешной |
Доказательство: |
Предположим обратное. Тогда рассмотрим 2 случая:
|
Из двух предыдущих лемм напрямую следует корректность алгоритма.
Сложность
При неправильной реализации алгоритма, используя вместо очередей дек и добавляя вершины из в начало дека, алгоритм в худшем случае будет работать за экспоненциальное время, так делать не рекомендуется.
иВ плохих случаях алгоритм Левита работает за лексикографическом порядке:
. Рассмотрим полный граф с вершинами и такими рёбрами, идущими в- для всех вершин вес ребра , т.е. количество вершин между и ; ,
- ребро веса ,
- для всех вершин вес ребра ; от до вершины расстояние равно .
Ясно, что кратчайший путь до каждой вершины равен Форда Беллмана и не многим уступает алгоритму Дейкстры.
, но в плохом случае алгоритм при подсчёте вершины будет пересчитывать все вершины до неё (кроме первой). На шаге в очередь положат вершины от до , причём вершину из больше не достанут. На следующем шаге добавлений не произойдёт, так как вершины больше уже в очереди. На шаге алгоритм улучшит расстояние до вершины на (что видно из веса рёбер и , равных и соответственно), так что её добавят в и обработают на шаге (релаксаций не происходит). На следующем шаге из обычной очереди достанут вершину , расстояние до неё, равное , на меньше, чем расстояние до и вершин. Их добавят в срочную очередь, но так как , то после подсчёта вершины вершину снова добавят в . Затем дойдёт очередь до вершины , что вызовет релаксацию предыдущих вершин , затем прорелаксируют вершины , и после вершина . Аналогично будут происходить релаксации всех вершин при обработке вершины из очереди . Таким образом, вершину будут добавлять в срочную очередь раз (добавление вершин из очереди с номером больше ) количество добавлений "старшей" вершины . Количество добавлений вершины составит , а сумма всех добавлений примерно составит . При обработке каждой вершины приходится обходить ребер, что даёт оценку . Однако, на реальных графах алгоритм Левита работает быстрее, чем алгоритмСм. также
- Алгоритм A*
- Алгоритм Дейкстры
- Алгоритм Джонсона
- Алгоритм Флойда
- Алгоритм Форда-Беллмана
- Обход в ширину
Источники информации
- Википедия — Алгоритм Левита
- MAXimal :: algo :: Алгоритм Левита
- И. В. Романовский, Дискретный анализ, ISBN 5-7940-0138-0; 2008 г., 4 издание, стр. 229-231.