Это не официальный сайт wikipedia.org 01.01.2023

Алгоритм Дейкстры — Википедия

Алгоритм Дейкстры

Алгори́тм Де́йкстры (англ. Dijkstra’s algorithm) — алгоритм на графах, изобретённый нидерландским учёным Эдсгером Дейкстрой в 1959 году. Находит кратчайшие пути от одной из вершин графа до всех остальных. Алгоритм работает только для графов без рёбер отрицательного веса. Алгоритм широко применяется в программировании, например, его используют протоколы маршрутизации OSPF и IS-IS.

Блок-схема алгоритма Дейкстры.

Формулировка задачиПравить

ПримерыПравить

Вариант 1. Дана сеть автомобильных дорог, соединяющих города Московской области. Некоторые дороги односторонние. Найти кратчайшие пути от города А до каждого города области (если двигаться можно только по дорогам).

Вариант 2. Имеется некоторое количество авиарейсов между городами мира, для каждого известна стоимость. Стоимость перелёта из A в B может быть не равна стоимости перелёта из B в A. Найти маршрут минимальной стоимости (возможно, с пересадками) от Копенгагена до Барнаула.

Формальное определениеПравить

Дан взвешенный ориентированный[1] граф G ( V , E )   без дуг отрицательного веса[2]. Найти кратчайшие пути от некоторой вершины a   графа G   до всех остальных вершин этого графа.

Неформальное объяснение от Давтяна ТигранаПравить

Каждой вершине из V сопоставим метку — минимальное известное расстояние от этой вершины до a.

Алгоритм работает пошагово — на каждом шаге он «посещает» одну вершину и пытается уменьшать метки.

Работа алгоритма завершается, когда все вершины посещены.

Инициализация.

Метка самой вершины a полагается равной 0, метки остальных вершин — бесконечности.

Это отражает то, что расстояния от a до других вершин пока неизвестны.

Все вершины графа помечаются как непосещённые.

Шаг алгоритма.

Если все вершины посещены, алгоритм завершается.

В противном случае, из ещё не посещённых вершин выбирается вершина u, имеющая минимальную метку.

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

Если полученное значение длины меньше значения метки соседа, заменим значение метки полученным значением длины. Рассмотрев всех соседей, пометим вершину u как посещённую и повторим шаг алгоритма.

ПримерПравить

Рассмотрим выполнение алгоритма на примере графа, показанного на рисунке.

Пусть требуется найти кратчайшие расстояния от 1-й вершины до всех остальных.

 

Кружками обозначены вершины, линиями — пути между ними (рёбра графа).

В кружках обозначены номера вершин, над рёбрами обозначен их вес — длина пути.

Рядом с каждой вершиной красным обозначена метка — длина кратчайшего пути в эту вершину из вершины 1.

 

Первый шаг.

Минимальную метку имеет вершина 1. Её соседями являются вершины 2, 3 и 6.

 

Первый по очереди сосед вершины 1 — вершина 2, потому что длина пути до неё минимальна.

Длина пути в неё через вершину 1 равна сумме значения метки вершины 1 и длины ребра, идущего из 1-й в 2-ю, то есть 0 + 7 = 7.

Это меньше текущей метки вершины 2, бесконечности, поэтому новая метка 2-й вершины равна 7.

 

Аналогичную операцию проделываем с двумя другими соседями 1-й вершины — 3-й и 6-й.

 

Все соседи вершины 1 проверены.

Текущее минимальное расстояние до вершины 1 считается окончательным и пересмотру не подлежит.

Вычеркнем её из графа, чтобы отметить, что эта вершина посещена.

 

Второй шаг.

Снова находим «ближайшую» из непосещённых вершин. Это вершина 2 с меткой 7.

 

Снова пытаемся уменьшить метки соседей выбранной вершины, пытаясь пройти в них через 2-ю вершину. Соседями вершины 2 являются вершины 1, 3 и 4.

Первый (по порядку) сосед вершины 2 — вершина 1. Но она уже посещена, поэтому с 1-й вершиной ничего не делаем.

Следующий сосед — вершина 3, так как имеет минимальную метку.

Если идти в неё через 2, то длина такого пути будет равна 17 (7 + 10 = 17). Но текущая метка третьей вершины равна 9, а это меньше 17, поэтому метка не меняется.

 

Ещё один сосед вершины 2 — вершина 4.

Если идти в неё через 2-ю, то длина такого пути будет равна сумме кратчайшего расстояния до 2-й вершины и расстояния между вершинами 2 и 4, то есть 22 (7 + 15 = 22).

Поскольку 22<  , устанавливаем метку вершины 4 равной 22.

 

Все соседи вершины 2 просмотрены, замораживаем расстояние до неё и помечаем её как посещённую.

 

Третий шаг.

Повторяем шаг алгоритма, выбрав вершину 3. После её «обработки» получим такие результаты:

 

Дальнейшие шаги.

Повторяем шаг алгоритма для оставшихся вершин. Это будут вершины 6, 4 и 5, соответственно порядку.

     

Завершение выполнения алгоритма.

Алгоритм заканчивает работу, когда все вершины посещены.

Результат работы алгоритма виден на последнем рисунке: кратчайший путь от вершины 1 до 2-й составляет 7, до 3-й — 9, до 4-й — 20, до 5-й — 20, до 6-й — 11.

Если в какой-то момент все непосещённые вершины помечены бесконечностью, то это значит, что до этих вершин нельзя добраться (то есть граф несвязный). Тогда алгоритм может быть завершён досрочно.

АлгоритмПравить

ОбозначенияПравить

  • V   — множество вершин графа
  • E   — множество рёбер графа
  • w [ i j ]   — вес (длина) ребра i j  
  • a   — вершина, расстояния от которой ищутся
  • U   — множество посещённых вершин
  • d [ u ]   — по окончании работы алгоритма равно длине кратчайшего пути из a   до вершины u  
  • p [ u ]   — по окончании работы алгоритма содержит кратчайший путь из a   в u  
  • v   — текущая вершина, рассматриваемая алгоритмом

Код реализации алгоритмаПравить

Ниже приведён код реализации алгоритма на языке программирования Java. Данный вариант реализации не является лучшим, но наглядно показывает возможную реализацию алгоритма:

class Dijkstra {
	double[] dist = new double[GV()];
	Edge[] pred = new Edge[GV()];
	public Dijkstra(WeightedDigraph G, int s) {
		boolean[] marked = new boolean[GV()];
		for (int v = 0; v <GV(); v++)
			dist[v] = Double.POSITIVE_INFINITY;
		MinPQplus<Double, Integer> pq;
		pq = new MinPQplus<Double, Integer>(); \\Priority Queue
		dist[s] = 0.0;
		pq.put(dist[s], s);
		while (!pq.isEmpty()) {
			int v = pq.delMin();
				if (marked[v]) continue;
			marked(v) = true;
			for (Edge e  (v)) {
				int w = e.to();
				if (dist[w]> dist[v] + e.weight()) {
					dist[w] = dist[v] + e.weight();
					pred[w] = e;
					pq.insert(dist[w], w);
				}
			}
		}
	}
}

ПсевдокодПравить

Присвоим

d [ a ] 0 ,   p [ a ] 0  

Для всех u V   отличных от a   присвоим d [ u ]  .

Пока v U  . Пусть v U   — вершина с минимальным d [ v ]   занесём v   в U  

Для всех u U   таких, что v u E  

если d [ u ] > d [ v ] + w [ v u ]   то

изменим d [ u ] d [ v ] + w [ v u ]  

изменим p [ u ] ( p [ v ] , u )  

ОписаниеПравить

В простейшей реализации для хранения чисел d[i] можно использовать массив чисел, а для хранения принадлежности элемента множеству U — массив булевых переменных.

В начале алгоритма расстояние для начальной вершины полагается равным нулю, а все остальные расстояния заполняются большим положительным числом (бо́льшим максимального возможного пути в графе). Массив флагов заполняется нулями. Затем запускается основной цикл.

На каждом шаге цикла мы ищем вершину v   с минимальным расстоянием и флагом равным нулю. Затем мы устанавливаем в ней флаг в 1 и проверяем все соседние с ней вершины u  . Если в них (в u  ) расстояние больше, чем сумма расстояния до текущей вершины и длины ребра, то уменьшаем его. Цикл завершается, когда флаги всех вершин становятся равны 1, либо когда у всех вершин c флагом 0 d [ i ] =  . Последний случай возможен тогда и только тогда, когда граф G несвязный.

Доказательство корректностиПравить

Пусть l ( v )   — длина кратчайшего пути из вершины a   в вершину v  . Докажем по индукции, что в момент посещения любой вершины z   выполняется равенство d ( z ) = l ( z )  .

База. Первой посещается вершина a  . В этот момент d ( a ) = l ( a ) = 0  .

Шаг. Пусть мы выбрали для посещения вершину z a  . Докажем, что в этот момент d ( z ) = l ( z )  . Для начала отметим, что для любой вершины v   всегда выполняется d ( v ) l ( v )   (алгоритм не может найти путь короче, чем кратчайший из всех существующих). Пусть P   — кратчайший путь из a   в z  . Вершина z   находится на P   и не посещена. Поэтому множество непосещённых вершин на P   непусто. Пусть y   — первая непосещённая вершина на P  , x   — предшествующая ей (следовательно, посещённая). Поскольку путь P   кратчайший, его часть, ведущая из a   через x   в y  , тоже кратчайшая, следовательно l ( y ) = l ( x ) + w ( x y )  .

По предположению индукции, в момент посещения вершины x   выполнялось d ( x ) = l ( x )  , следовательно, вершина y   тогда получила метку не больше чем d ( x ) + w ( x y ) = l ( x ) + w ( x y ) = l ( y )  . Следовательно, d ( y ) = l ( y )  . Если z = y  , то индукционный переход доказан. Иначе, поскольку сейчас выбрана вершина z  , а не y  , метка z   минимальна среди непосещённых, то есть d ( z ) d ( y ) = l ( y ) l ( z )  . Комбинируя это с d ( z ) l ( z )  , имеем d ( z ) = l ( z )  , что и требовалось доказать.

Поскольку алгоритм заканчивает работу, когда все вершины посещены, в этот момент d = l   для всех вершин.

Сложность алгоритмаПравить

Сложность алгоритма Дейкстры зависит от способа нахождения вершины v, а также способа хранения множества непосещённых вершин и способа обновления меток. Обозначим через n количество вершин, а через m — количество рёбер в графе G.

  • В простейшем случае, когда для поиска вершины с минимальным d[v] просматривается всё множество вершин, а для хранения величин d используется массив, время работы алгоритма есть O ( n 2 )  . Основной цикл выполняется порядка n раз, в каждом из них на нахождение минимума тратится порядка n операций. На циклы по соседям каждой посещаемой вершины тратится количество операций, пропорциональное количеству рёбер m (поскольку каждое ребро встречается в этих циклах ровно дважды и требует константное число операций). Таким образом, общее время работы алгоритма O ( n 2 + m )  , но, так как m n ( n 1 )  , оно составляет O ( n 2 )  .
  • Для разреженных графов (то есть таких, для которых m много меньше n²) непосещённые вершины можно хранить в двоичной куче, а в качестве ключа использовать значения d[i], тогда время удаления вершины из U ¯   станет log n   при том, что время модификации d [ i ]   возрастёт до log n  . Так как цикл выполняется порядка n раз, а количество смен меток не больше m, время работы такой реализации — O ( n log n + m log n )  .
  • Если для хранения непосещённых вершин использовать фибоначчиеву кучу, для которой удаление происходит в среднем за O ( log n )  , а уменьшение значения в среднем за O ( 1 )  , то время работы алгоритма составит O ( n log n + m )  . Однако согласно лекциям Алексеева и Таланова[3]:

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

Альтернативами им служат толстые кучи, тонкие кучи и кучи Бродала (англ.), обладающие теми же асимптотическими оценками, но меньшими константами[4].

См. такжеПравить

ПримечанияПравить

  1. Частными случаями ориентированного графа являются неориентированный и смешанный («частично ориентированный») графы.
  2. Для графа с отрицательными весами применяется более общий алгоритм — Алгоритм Дейкстры с потенциалами
  3. Владимир Алексеев, Владимир Таланов, Курс «Структуры данных и модели вычислений», Лекция № 7: Биномиальные и фибоначчиевы кучи // 26.09.2006, Интуит.ру
  4. Владимир Алексеев, Владимир Таланов, Курс «Структуры данных и модели вычислений», Лекция № 8: Тонкие кучи // 26.09.2006, Интуит.ру

ЛитератураПравить

СсылкиПравить