Эвристики для поиска кратчайших путей — различия между версиями
Xottab (обсуждение | вклад) м (→Калибровка(caliber)) |
Xottab (обсуждение | вклад) м (→Алгоритм A*) |
||
Строка 96: | Строка 96: | ||
Приведём немного изменённую версию этого алгоритма. | Приведём немного изменённую версию этого алгоритма. | ||
− | Возьмём функцию <tex>h(v): V \rightarrow \mathbb{R} </tex> - <b><i>потенциал</i></b> вершины. Тогда, с её помощью можно определить <b><i> | + | Возьмём функцию <tex>h(v): V \rightarrow \mathbb{R} </tex> - <b><i>потенциал (potential)</i></b> вершины. Тогда, с её помощью можно определить <b><i>потенциальную стоимость (reduced cost)</i></b> каждого ребра как <tex>\ell_{h}(v,w) = \ell(v,w)-h(v)+h(w)</tex> |
Заметим, что замена <tex>\ell</tex> на <tex>\ell_{h}</tex> не изменит кратчайших путей: возьмём любой путь <tex>P = (s=v_{0},v_{1},...,v_{k},v_{k+1}=t)</tex>. Тогда <tex>\ell_{h}(P) = \ell_{h}(s,v_{1}), \ell_{h}(s,v_{w})</tex>. Тогда <tex>\ell_{h}(P) = \ell_{h}(s,v_{1})+\ell_{h}(v_{1},v_{2})+...+\ell_{h}(v_{k},t) =</tex> <tex> \ell(s,v_{1})-h(s)+h(v_{1})+\ell(v_{1},v_{2})-h(v_1)+h(v_{2})+...+\ell(v_{k},t)-h(v_k)+h(t) = </tex> <tex>\ell(P)-h(s)+h(t)</tex>. | Заметим, что замена <tex>\ell</tex> на <tex>\ell_{h}</tex> не изменит кратчайших путей: возьмём любой путь <tex>P = (s=v_{0},v_{1},...,v_{k},v_{k+1}=t)</tex>. Тогда <tex>\ell_{h}(P) = \ell_{h}(s,v_{1}), \ell_{h}(s,v_{w})</tex>. Тогда <tex>\ell_{h}(P) = \ell_{h}(s,v_{1})+\ell_{h}(v_{1},v_{2})+...+\ell_{h}(v_{k},t) =</tex> <tex> \ell(s,v_{1})-h(s)+h(v_{1})+\ell(v_{1},v_{2})-h(v_1)+h(v_{2})+...+\ell(v_{k},t)-h(v_k)+h(t) = </tex> <tex>\ell(P)-h(s)+h(t)</tex>. | ||
Строка 102: | Строка 102: | ||
Таким образом длины все путей <tex>s \rightsquigarrow t</tex> изменятся на одну и ту же величину <tex>h(t)-h(s)</tex> | Таким образом длины все путей <tex>s \rightsquigarrow t</tex> изменятся на одну и ту же величину <tex>h(t)-h(s)</tex> | ||
− | В нашем случае, алгоритм A* будет эквивалентен алгоритму Дейкстры, на графе <tex>G_{h}</tex>, у которого стоимости рёбер заменили на их | + | В нашем случае, алгоритм A* будет эквивалентен алгоритму Дейкстры, на графе <tex>G_{h}</tex>, у которого стоимости рёбер заменили на их потенциальные стоимости. На каждом шаге необходимо будет выбирать из очереди вершину <tex>v</tex> с минимальным значением <tex>\ell(P_{s \rightsquigarrow v})-h(s)+h(v)</tex>. Очевидно, <tex>h(s)</tex> будет одинаковым для любой вершины <tex>v</tex>. |
− | Назовём функцию <tex>h</tex> <b>правдоподобной</b>, если <tex>\forall (u,v): \ell_{h}(u,v) \geqslant 0</tex>. Известно, что, если <tex>h(t)\leqslant 0</tex> и <tex>h</tex> правдоподобна, то для любого <tex>v</tex>, <tex>h(v)</tex> - нижняя граница <tex>dist(v,t)</tex> | + | Назовём функцию <tex>h</tex> <b>правдоподобной (<i>feasible</i>)</b>, если <tex>\forall (u,v): \ell_{h}(u,v) \geqslant 0</tex>. Известно, что, если <tex>h(t)\leqslant 0</tex> и <tex>h</tex> правдоподобна, то для любого <tex>v</tex>, <tex>h(v)</tex> - нижняя граница <tex>dist(v,t)</tex> |
− | Главное отличие от алгоритма Дейкстры в том, что A* является <b> | + | Главное отличие от алгоритма Дейкстры в том, что A* является <b>информированным</b> алгоритмом - он обрабатывает в первую очередь те вершины, которые находятся ближе к результату. |
Скорость работы алгоритма A*: | Скорость работы алгоритма A*: | ||
* в худшем случае - <tex>h(v)=0</tex> - вырождается в алгоритм Дейкстры | * в худшем случае - <tex>h(v)=0</tex> - вырождается в алгоритм Дейкстры | ||
* в лучшем случае - <tex>\forall v: h(v)=dist(v,t)</tex> | * в лучшем случае - <tex>\forall v: h(v)=dist(v,t)</tex> | ||
− | **<tex>\ell_{h}(v,w)=0</tex>, если ребро <tex>(v,w)</tex> лежит на кратчайшем пути, иначе | + | **<tex>\ell_{h}(v,w)=0</tex>, если ребро <tex>(v,w)</tex> лежит на кратчайшем пути, иначе потенциальная стоимость положительна |
**все посещённые вершины будут лежать на кратчайшем пути | **все посещённые вершины будут лежать на кратчайшем пути | ||
== Двунаправленный A*== | == Двунаправленный A*== | ||
Строка 119: | Строка 119: | ||
* <tex>p_{r}(v)</tex>, оценивающая <tex>dist(s,v)</tex> | * <tex>p_{r}(v)</tex>, оценивающая <tex>dist(s,v)</tex> | ||
− | В этом случае появляется дополнительная проблема: различные | + | В этом случае появляется дополнительная проблема: различные потенциальные стоимости у рёбер для различных обходов: |
*<tex>\ell_{p_{f}}(v,w) = \ell(v,w)-p_{f}(v)+p_{f}(w)</tex> - если ребро обрабатывается в обходе, начатом в <tex>s</tex> | *<tex>\ell_{p_{f}}(v,w) = \ell(v,w)-p_{f}(v)+p_{f}(w)</tex> - если ребро обрабатывается в обходе, начатом в <tex>s</tex> | ||
*<tex>\ell_{p_{r}}(v,w) = \ell(v,w)-p_{r}(w)+p_{r}(v)</tex> - если ребро обрабатывается в обходе, начатом в <tex>t</tex> | *<tex>\ell_{p_{r}}(v,w) = \ell(v,w)-p_{r}(w)+p_{r}(v)</tex> - если ребро обрабатывается в обходе, начатом в <tex>t</tex> |
Версия 17:39, 10 декабря 2013
Данная статья - перевод выступления Renato F. Werneck в Microsoft Data Structures and Algorithms School в 2010 году.
Содержание
Проблема поиска кратчайшего пути
Дано:
- ориентированный граф
- отправная точка - вершина , пункт назначения - вершина
Цель: найти кратчайший путь
Мы будем рассматривать сеть автомобильных дорог:
- - множество перекрёстков
- - множество дорог
- - среднее время, которое занимает проезд по дороге
Алгоритм Дейкстры
основная статья: Алгоритм Дейкстры
- на каждом шаге выбирает из множества непросмотренных вершин вершину с наименьшим расстоянием до старта и релаксирует рёбра, исходящие из неё
- завершает свою работу, когда цель достигнута (или просмотрены все вершины)
Скорость работы алгоритма Дейкстры сильно зависит от скорости операций с приоритетной очередью.
Поскольку мы рассматриваем сеть автомобильных дорог, то
(граф планарен почти везде).Для фибоначчиевых куч время работы алгоритма составляет
, для двоичных куч:Но на практике чаще используются 2-, 4- и 8-ичные кучи: они более простые, оценка времени работы содержит меньшее количество скрытых констант.
Улучшения алгоритма Дейкстры
Многоуровневые корзины (multi-level buckets, MLB)
Структура данных | Время работы (сек) |
---|---|
Двоичная куча | 12,38 |
4-куча | 11,53 |
8-куча | 11,52 |
MLB | 9,36 |
MLB + калибровка | 8,04 |
Подходит только графов с целочисленными рёбрами.
- Будем складывать вершины в "корзины"
- Наша структура данных будет поддерживать индекс
- На каждом шаге алгоритма, если пусто, то увеличим , а иначе достанем одну вершину из
- При релаксации будем убирать вершину из исходной корзины и класть в корзину, соответствующую новому значению
Можно заметить, что при такой реализации, все операции с приоритетной очередью будут выполняться за
. Тогда, для одного уровня корзин время работы алгоритма Дейкстры можно оценить как , где - максимальная длина ребра в графе.При двухуровневой реализации будем поддерживать два уровня корзин: первый уровень будет соответствовать одноуровневой реализации, а корзины второго уровня будут содержать диапазон значений корзин первого уровня, которые в них входят.
Соответственно, нам нужно поддерживать два индекса
и для каждого из уровней соответственно.При такой реализации, время работы алгоритма Дейкстры можно оценить как
Калибровка (caliber)
Введём величину калибр (caliber) вершины
- вес минимального ребра, входящего в , или , если в вершину не входит ни одно ребро. Будем говорить, что текущее значение точно, если оно равно длине пути .Лемма (1): |
Предположим, что длины рёбер неотрицательны. Пусть - минимальное из текущих значений . Тогда, если существует такая вершина , что , то текущее значение точно. |
Эта лемма позволяет нам смягчить правило выбора текущей вершины в алгоритме Дейкстры, при этом сохраняя инвариант(почти все вершины обрабатываются единожды). Калибровка использует Лемму 1 чтобы находить и обрабатывать вершины с точными текущими значениями расстояния до них.
Модифицируем нашу MLB - структуру: будем хранить помеченные вершины в двух группах: сет
и приоритетная очередь , реализованная на MLB. Алгоритм, приведённый ниже, называется алгоритмом умной очереди (smart queue).Вершины в
будут иметь точные метки. Если непусто, мы удалим оттуда вершину и прорелаксируем всех её соседей. Если же пусто, мы достанем из вершину с минимальной меткой и прорелаксируем всех её соседей.Рассмотрим механизм релаксации: пусть мы уменьшаем
. Заметим, что в этом случае не могло лежать в (иначе было не точно). Если - применим к . Эта операция либо переместила внутри , либо определила, что метка точна и переместила в . Если же , мы применим операцию , и запишется в или , в зависимости от того, выполняется ли условие леммы.Двунаправленный поиск
Мы можем уменьшить количество посещённых вершин в алгоритме Дейкстры, просто запустив его и из начальной и из конечной вершины. Такая эвристика не испортит скорость работы в худшем случае.
Создадим две приоритетных очереди и запустим на одной из них алгоритм Дейкстры, ищущий
из , а на другой - ищущий из . Алгоритм завершит свою работу, когда какая-нибудь вершина будет удалена из обоих очередей.Тонкость этого алгоритма заключается в том, что кратчайший путь
не обязательно пройдёт через вершину . Поэтому после остановки двунаправленного поиска, нам необходимо перебрать все рёбра из вершин, имеющих в вершины с и найти ребро с минимальным . Если эта величина меньше, чем длина первоначально найденного пути - то это и есть результат работы алгоритма.На практике, такой двунаправленный поиск быстрее обычного алгоритма Дейкстры примерно в два раза.
Алгоритм A*
основная статья: Алгоритм A*
Приведём немного изменённую версию этого алгоритма.
Возьмём функцию
- потенциал (potential) вершины. Тогда, с её помощью можно определить потенциальную стоимость (reduced cost) каждого ребра какЗаметим, что замена
на не изменит кратчайших путей: возьмём любой путь . Тогда . Тогда .Таким образом длины все путей
изменятся на одну и ту же величинуВ нашем случае, алгоритм A* будет эквивалентен алгоритму Дейкстры, на графе
, у которого стоимости рёбер заменили на их потенциальные стоимости. На каждом шаге необходимо будет выбирать из очереди вершину с минимальным значением . Очевидно, будет одинаковым для любой вершины .Назовём функцию
правдоподобной (feasible), если . Известно, что, если и правдоподобна, то для любого , - нижняя границаГлавное отличие от алгоритма Дейкстры в том, что A* является информированным алгоритмом - он обрабатывает в первую очередь те вершины, которые находятся ближе к результату.
Скорость работы алгоритма A*:
- в худшем случае - - вырождается в алгоритм Дейкстры
- в лучшем случае -
- , если ребро лежит на кратчайшем пути, иначе потенциальная стоимость положительна
- все посещённые вершины будут лежать на кратчайшем пути
Двунаправленный A*
Для двунаправленной версии алгоритма нам нужны две потенциальные функции:
- , оценивающая
- , оценивающая
В этом случае появляется дополнительная проблема: различные потенциальные стоимости у рёбер для различных обходов:
- - если ребро обрабатывается в обходе, начатом в
- - если ребро обрабатывается в обходе, начатом в
Чтобы избежать этой проблемы, необходимо, чтобы
. Кроме того, функции должны бить монотонными.Решение - использовать усреднённые потенциальные функции:
При таком выборе потенциальных функций, выполняется
и тогда двунаправленный A* становится аналогичен двунаправленному алгоритму ДейкстрыДвухэтапные алгоритмы
К сожалению, двунаправленный алгоритм Дейкстры всего в два раза быстрее обычного, а это слишком медленно. Рассмотрим алгоритм поиска кратчайшего пути, состоящий из двух этапов:
- Препроцессинг
- запускается единожды для графа
- может занимать много времени
- рассчитывает некую вспомогательную информацию
- Запрос
- может использовать данные, полученные во время препроцессинга
- запускается по требованию для пары
- должен выполняться очень быстро (в реальном времени)
Можно рассмотреть в этом ключе два примера:
- Алгоритм Дейкстры: препроцессинг - ничего не делать, запрос - выполнение алгоритма Дейкстры;
- Полный перебор: препроцессинг - посчитать таблицу расстояний размером (займёт порядка 5 лет времени и 1 петабайта памяти для карты Европы), запрос - обратиться к элементу таблицы.
Оба эти примера - крайние случаи. Нам нужно нечто более гибкое: препроцессинг за часы/минуты, рост количества предпосчитанных данных линейно от размера графа и запросы в реальном времени.
ALT
Аббревиатура ALT расшифровывается как A* +Landmarks + Triangle inequality : A* + ориентиры + неравенство треугольника.
Препроцессинг:
- взять небольшое количество вершин (например, 16), обозначив их как ориентиры (landmarks)
- для каждого из ориентиров посчитать кратчайшие пути до всех вершин
- сохранить эти пути
Запрос:
- используем A*
- если некоторое ребро находится на кратчайшем пути между исходной точкой и ориентиром - по нему идём в первую очередь
Будем использовать неравенство треугольника для нижних оценок пути:
Эта эвристика хорошо работает, на дорожных графах, для которых верно следующее: как правило, кратчайший путь затрагивает небольшое количество локальных дорог, потом крупную автомагистраль и снова некоторое количество локальных дорог.
Выбор ориентиров
Сложности в выборе ориентиров:
- хороший ориентир для запроса должен находиться "до" (точно не будет общих рёбер на кратчайшем пути) или "за" (чем острее угол, тем меньше отклонение от предварительно посчитанного кратчайшего пути до искомого)
- Нам нужно выбрать такие ориентиры, которые будут неплохими для всех запросов.
Выглядит логичным выбирать ориентиры на краю дорожной сети.
Существуют различные способы выбора ориентиров:
Случайный выбор (random)
- как следует из названия, ориентиры выбираются случайным образом
Плоскостной (planar)
- разделим карту на секторов одинаковой площади
- возьмём ориентиром наиболее удалённую точку от центра в каждом секторе
Такой способ подходит, только если граф имеет относительно правильную форму. На практике почти не используется.