Алгоритм 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();
