Дейкстра В Линейное Время

Приветствую всех, а особенно тех, кто интересуется проблемами дискретной математики и теории графов.



Фон

Так уж получилось, что, движимый интересом, я разрабатывал сервис по построению туров.

маршруты.

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

Ну и одной из подзадач было посчитать время в пути от одного заведения до другого.

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

вариант, только в одной только Москве заведений было более 10к, а решения типа манхэттенской дистанции в наших городах просто не работают вообще.

И вот получилось, что проблема производительности в задаче комбинаторики решена, но большую часть времени на обработку запроса уходит на поиск некэшированных путей.

Проблема осложнялась тем, что граф дорог Osm в Москве довольно большой (полмиллиона узлов и 1,1 миллиона дуг).

Я не буду рассказывать обо всех попытках и о том, что на самом деле проблему можно было решить, отсекая лишние дуги графа, я просто расскажу о том, что в какой-то момент меня осенило и я понял, что если подойти к алгоритму Дейкстры с точки зрения вероятностного подхода она может быть линейной.



Дейкстра в логарифмическом времени

Все знают, но никто не знает читать что алгоритм Дейкстры, используя очередь с логарифмической сложностью вставки и удаления, может привести к сложности вида O(n журнал (п) + м журнал(н)).

При использовании кучи Фибоначчи сложность можно снизить до O(n*log(n) + m), но она всё равно не линейна, а мне бы хотелось, чтобы так было.

В целом алгоритм очереди описывается следующим образом: Пусть будет:

  • V — множество вершин графа
  • E — множество ребер графа
  • w[i, j] — вес ребра от узла i до узла j
  • а — начальная вершина поиска
  • q -очередь вершин
  • d[i] — расстояние до i-го узла
  • d[a] = 0, для всех остальных d[i] = +inf
Пока q не пусто:
  • v — вершина с минимумом d[v] из q
  • Для всех вершин u, для которых имеется переход в E из вершины v
    • если d[u] > w[vu] + d[v]
      • удалить u из q на расстояние d[u]
      • d[u] = w[vu] + d[v]
      • добавить u к q с расстоянием d[u]
  • удалить v из q
Если в качестве очереди использовать красно-черное дерево, где на вставку и удаление требуется log(n), а поиск минимального элемента аналогичен log(n), то сложность алгоритма составит O(n журнал (п) + м журнал(н)).

И здесь стоит отметить одну важную особенность: ничто не мешает теоретически рассмотреть пик несколько раз.

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

Но стоит отметить, что одна вершина должна рассматриваться более 1 раза с некоторой малой вероятностью.



Сортировка хеш-таблицы

Чтобы сократить время работы алгоритма Дейкстры до линейного, предлагается структура данных, представляющая собой хеш-таблицу с номерами узлов (node_id) в качестве значений.

Замечу, что необходимость в массиве d никуда не делась; все еще необходимо получить расстояние до i-го узла за постоянное время.

На рисунке ниже показан пример того, как работает предложенная структура.



Дейкстра в линейное время

Опишем предлагаемую структуру данных пошагово:

  • узел u записывается в ячейку с номером, равным d[u] // Bucket_size, где Bucket_size — вместимость ячейки (например, 20 метров, т.е.

    в ячейке номер 0 будут находиться узлы, расстояние которых будет в диапазоне [ 0, 20) метров )

  • Минимальным узлом будем считать последний узел первой непустой ячейки, т.е.

    операция извлечения минимального элемента будет выполняться за O(1).

    Необходимо поддерживать актуальное состояние номера-идентификатора первой непустой ячейки (min_el).

  • операция вставки осуществляется путем добавления нового номера узла в конец ячейки и также выполняется за O(1), т.к.

    номер ячейки вычисляется за постоянное время.

  • операция удаления, как и в случае с хеш-таблицей, возможна посредством обычного перечисления, и можно было бы сделать предположение и сказать, что при небольшом размере ячейки это тоже O(1).

    Если вы не против памяти (в принципе, столько и не нужно, просто еще один массив на n), то можно создать массив позиций в ячейке.

    В этом случае, если удаляется элемент в середине ячейки, необходимо переместить последнее значение в ячейке на место удаления.

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

    В этом случае удаление узла v из очереди q необходимо производить только после рассмотрения всех соседних узлов.

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



Результаты измерений

Проверки производились на osm карте Москвы, выгруженной через osm2po в postgres, а затем загруженной в память.

Тесты были написаны на Java. Было 3 варианта графика:

  • исходный граф — 0,43 миллиона узлов, 1,14 миллиона дуг
  • сжатая версия графа со 173 тыс.

    узлов и 750 тыс.

    дуг

  • пешеходная версия сжатой версии графа, 450 тыс.

    дуг, 100 тыс.

    узлов.

Ниже картинка с измерениями на разных вариантах графика:

Дейкстра в линейное время

Рассмотрим зависимость вероятности повторного просмотра вершины от размера графа:
количество просмотров узла количество вершин графа вероятность повторения узла
104915 100015 4.8
169429 167892 0.9
431490 419594 2.8
Можно отметить, что вероятность не зависит от размера графа и достаточно специфична для запроса, но невелика и ее диапазон настраивается путем изменения мощности ячейки.

Буду очень признателен за помощь в построении вероятностной модификации алгоритма с параметрами, гарантирующими доверительный интервал, в пределах которого вероятность повторного просмотра не превысит определенного процента.

Также были проведены качественные измерения для практического подтверждения сравнения корректности результатов алгоритмов с новой структурой данных, которые показали полное совпадение длины кратчайшего пути от 1000 случайных узлов до 1000 других случайных узлов в графе.

.

(и так 250 итераций) при работе с сортирующей хеш-таблицей и красно-черным деревом.

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

По крайней мере, насколько я понимаю, они предлагают другой подход, основанный на построении минимального связующего дерева.

P.S.S. В течение недели постараюсь найти время и провести сравнение с кучей Фибоначчи и чуть позже добавлю примеры и тестовые коды в репозиторий на github. Теги: #Дийкстра #кратчайший путь #Алгоритмы

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.