Алгоритм D*
Алгоритм D* — алгоритм поиска кратчайшего пути во взвешенном ориентированном графе, где структура графа неизвестна заранее или постоянно подвергается изменению. Разработан Свеном Кёнигом и Максимом Лихачевым в 2002 году.
Содержание
Алгоритм LPA*
Постановка задачи
Дан взвешенный ориентированный граф . Даны вершины и . Требуется после каждого изменения графа уметь вычислять функцию для каждой известной вершины
Описание
Функция
будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины до .Обозначим множество
как множество вершин, исходящих из вершины .Аналогично множество
как множество вершин, входящих в вершину .Функция
будет возвращать стоимость перехода из вершины в вершину . При этом .Если
Иначе
Вершина
может быть 3-х видов:- насыщена, если
- переполнена, если
- ненасыщена, если
Очевидно, что если все вершины насыщены, то мы можем найти расстояние от стартовой вершины до любой. Такой граф будем называть устойчивым (насыщенным).
Функция
, где - вершина, возвращает вектор из 2-ух значений , .- .
- .
Если в конце поиска пути
, то мы не смогли найти путь от до на текущей итерации. Но после следующего изменения графа путь вполне может найтись.Псевдокод
Основная функция, описывающая алгоритм
Main(): { Initialize(); while (true) { ComputeShortestPath(); В данный момент мы знаем кратчайший путь изв . Ждем каких-либо изменений графа. for всех ориентированных ребер с измененными весами: { Обновляем результат функции ; UpdateVertex( ); } } }
Теперь опишем составные элементы подробнее
Initialize(): { //Заведем приоритетную очередь, в которую будем помещать вершины. Сортировка будет производиться по функции . for U.Insert( ; CalcKey( )); }
//Функция. Возвращаемые значения сортируются в лексографическом порядке, т.е. сначала , потом CalcKey(s): { return [ ; ]; }
UpdateVertex(): { if ( ) if ( ) U.Remove(u); if ( ) U.Insert( ; CalcKey( )); }
// Функция неоднократно перерасчитывает значениеу ненасыщенных вершин. Такой перерасчет значения будем называть расширением вершины. ComputeShortestPath(): { while (U.TopKey() < CalcKey( ) OR rhs( )) u = U.Pop(); if (g(u) > rhs(u)) g(u) = rhs(u); for UpdateVertex(s); else g(u) = ; for UpdateVertex(s); }
Таким образом мы описали алгоритм LPA*. Он неоднократно определяет путь между вершинами
и , используя при этом данные из предыдущих итераций. Очевидно, что в худшем случае (а именно когда все ребра вокруг текущей вершины изменили свой вес) алгоритм будет работать как последовательные вызовы алгоритма А* за . Улучшим эту оценку с помощью алгоритма D* lite.Примечание: на практике же такой подход тоже имеет место на плотных графах (или матрицах), так как в среднем дает оценку
.Алгоритм D*
Пока что был описан только алгоритм LPA*. Он способен неоднократно определять кратчайшее расстояние между начальной и конечной вершинами при любом изменении данного графа. Его первоначальный поиск полностью совпадает с алгоритмом A*, но последующие итерации способны использовать информацию из предыдущих поисков.
Постановка задачи
Теперь на основе LPA* опишем алгоритм D*, который способен определять расстояние между текущей вершиной
, в которой, допустим, находится курсор/робот, и конечной вершиной при каждом изменении графа в то время, как наш робот движется вдоль найденного пути.Описание
Опишем первую версию алгоритма D*. Очевидно, что большинство вершин в процессе движения робота остаются неизменными, поэтому мы можем применить алгоритм LPA*.
Примечание: Большинство функций переходят в данный алгоритм без изменений, поэтому опишем только измененные части.
Для начала мы поменяем направление поиска в графе.
Теперь функция g(s) хранит минимальное известное расстояние от
до . Свойства остаются прежними.Эвристическая функция h(s,s') теперь должна быть неотрицательная и обратно-устойчивая, т.е.
и для всех и . Очевидно, что при движении робота изменяется, поэтому данные свойства должны выполняться для всех .Дополнительное условие выхода также меняется, т.е. при
путь не найден на данной итерации. Иначе путь найден и робот может проследовать по нему.Примечание: Так же следует отметить, что функция Initialize не обязана инициализировать абсолютно все вершины перед стартом алгоритма. Это важно, так как в на практике число вершин может быть огромным и только немногие будут пройдены робот в процессе движения. Так же это дает возможность добавления/удаления ребер без потери устойчивости всех подграфов данного графа.
Псевдокод (Первая версия)
При такой постановке задачи псевдокод не сильно меняется. Но функция Main все-таки претерпевает значительные изменения.
CalcKey(s): return [; ];
Initialize(): U =; for U.Insert( ; CalcKey( ));
UpdateVertex(u): if () rhs(u) = if ( ) U.Remove(u); if ( ) U.Insert(u; CalcKey(u));
ComputeShortestPath(): while (U.TopKey() < CalcKey() OR ) u = U.Pop(); if (g(u) > rhs(u)) g(u) = rhs(u); for UpdateVertex(s); else g(u) = ; for UpdateVertex(s);
Main(): Initialize(); ComputeShortestPath(); while () // if ( ) тогда путь на данной итерации не найден. = такая вершина s', что Передвинулись вдоль найденного пути и изменили вершину ; Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним. if (если граф изменился) for всех ориентированных ребер с измененными весами: Обновляем результат функции ; UpdateVertex(u); for U.Update( ; CalcKey( )); ComputeShortestPath();
Теорема (Свен Кёниг, Об устойчивой насыщенности вершин): |
Функция ComputeShortestPath в данной версии алгоритма расширяет вершину максимум 2 раза, а именно 1 раз, если вершина ненасыщена, и максимум 1 раз, если она перенасыщена. |