Алгоритм D* — различия между версиями
Kabanov (обсуждение | вклад) |
Kabanov (обсуждение | вклад) |
||
| Строка 32: | Строка 32: | ||
[[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и выполнять неравенство треугольника, т.е. | [[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и выполнять неравенство треугольника, т.е. | ||
| − | <tex>h(s_{goal},s_{goal}) = 0</tex> и <tex>h(s, s_{goal}) | + | <tex>h(s_{goal},s_{goal}) = 0</tex> и <tex>h(s, s_{goal}) \leqslant c(s,s') + h(s',s_{goal})</tex> для всех <tex>s \in V</tex> и <tex>s' \in Succ(s)</tex> |
Функция <tex>key(s)</tex>, где <tex>s</tex> - вершина, возвращает вектор из 2-ух значений <tex>k_1(s)</tex>, <tex>k_2(s)</tex>. | Функция <tex>key(s)</tex>, где <tex>s</tex> - вершина, возвращает вектор из 2-ух значений <tex>k_1(s)</tex>, <tex>k_2(s)</tex>. | ||
| Строка 125: | Строка 125: | ||
[[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и обратно-устойчивая, т.е. | [[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и обратно-устойчивая, т.е. | ||
| − | <tex>h(s_{start},s_{start}) = 0</tex> и <tex>h(s_{start}, s) | + | <tex>h(s_{start},s_{start}) = 0</tex> и <tex>h(s_{start}, s) \leqslant h(s_{start},s') + c(s',s)</tex> для всех <tex>s \in S</tex> и <tex>s' \in Pred(s)</tex>. Очевидно, что при движении робота <tex>s_{start}</tex> изменяется, поэтому данные свойства должны выполняться для всех <tex>s_{start} \in V</tex>. |
Дополнительное условие выхода также меняется, т.е. при <tex>g(s_{start}) = +\infty</tex> путь не найден на данной итерации. Иначе путь найден и робот может проследовать по нему. | Дополнительное условие выхода также меняется, т.е. при <tex>g(s_{start}) = +\infty</tex> путь не найден на данной итерации. Иначе путь найден и робот может проследовать по нему. | ||
| Строка 172: | Строка 172: | ||
Передвинулись вдоль найденного пути и изменили вершину <tex>s_{start}</tex>; | Передвинулись вдоль найденного пути и изменили вершину <tex>s_{start}</tex>; | ||
Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним. | Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним. | ||
| − | if ( | + | if (граф изменился) |
for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами: | for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами: | ||
Обновляем результат функции <tex>c(u; v)</tex>; | Обновляем результат функции <tex>c(u; v)</tex>; | ||
| Строка 185: | Строка 185: | ||
|statement=Функция '''ComputeShortestPath''' в данной версии алгоритма ''расширяет'' вершину максимум 2 раза, а именно 1 раз, если вершина ненасыщена, и максимум 1 раз, если она переполнена. | |statement=Функция '''ComputeShortestPath''' в данной версии алгоритма ''расширяет'' вершину максимум 2 раза, а именно 1 раз, если вершина ненасыщена, и максимум 1 раз, если она переполнена. | ||
}} | }} | ||
| + | |||
| + | == Описание (2 версия) == | ||
| + | В первой версии алгоритма была серьезная проблема в том, что для каждой вершины в приоритетной очереди нужно было обновлять ключ суммарно за <tex>O(n \cdot log(n))</tex>. Это дорогая операция, так как очередь может содержать огромное число вершин. Воспользуемся оригинальным методом поиска и изменим преобразим основной цикл, чтобы избежать постоянного перестроения очереди <tex>U</tex>. | ||
| + | |||
| + | Теперь эвристическая функция должна поддерживать неравенство треугольника для всех вершин <tex>s,s',s'' \in V</tex>, т.е. <tex>h(s,s'') \leqslant h(s, s') + h(s',s'')</tex>. Так же должно выполняться свойство <tex>h(s,s') \leqslant c^*(s,s')</tex>, где <tex>c^*(s,s')</tex> - стоимость перехода по кратчайшему пути из <tex>s</tex> в <tex>s'</tex>, при этом <tex>s</tex> и <tex>s'</tex> не должны быть обязательно смежными. Такие свойства не противоречат свойствами из первой версии, а лишь усиливают их. | ||
| + | |||
| + | === Псевдокод (Вторая версия) === | ||
| + | |||
| + | '''CalcKey'''(s): | ||
| + | return [<tex>\min(g(s);rhs(s)) + h(s_{start};s) + K_m</tex>;<tex>\min(g(s); rhs(s))</tex>]; | ||
| + | |||
| + | '''Initialize'''(): | ||
| + | U = <tex>\varnothing</tex>; | ||
| + | <tex>K_m = 0</tex> | ||
| + | for <tex>s \in S</tex> | ||
| + | <tex>rhs(s) = g(s) = +\infty</tex> | ||
| + | <tex>rhs(s_{goal}) = 0</tex> | ||
| + | U.Insert(<tex>s_{goal}</tex>; CalcKey(<tex>s_{goal}</tex>)); | ||
| + | |||
| + | '''UpdateVertex'''(u): | ||
| + | if (<tex>u \ne s_{goal}</tex>) | ||
| + | rhs(u) = <tex>min_{s' \in Succ(u)}(c(u,s')+g(s'));</tex> | ||
| + | if (<tex>u \in U</tex>) | ||
| + | U.Remove(u); | ||
| + | if (<tex>g(u) \ne rhs(u)</tex>) | ||
| + | U.Insert(u; CalcKey(u)); | ||
| + | |||
| + | '''ComputeShortestPath'''(): | ||
| + | while (U.TopKey() < CalcKey(<tex>s_{start}</tex>) OR <tex>rhs(s_{start}) \ne g(s_{start})</tex>) | ||
| + | <tex>K_{old} = U.TopKey()</tex>; | ||
| + | u = U.Pop(); | ||
| + | |||
| + | if (<tex>K_{old}</tex> < CalcKey(<tex>u</tex>)) | ||
| + | U.Insert(<tex>u</tex>;CalcKey(<tex>u</tex>)); | ||
| + | |||
| + | if (g(u) > rhs(u)) | ||
| + | g(u) = rhs(u); | ||
| + | for <tex>s \in Pred(u)</tex> | ||
| + | UpdateVertex(s); | ||
| + | else | ||
| + | g(u) = <tex>+\infty</tex>; | ||
| + | for <tex>s \in Pred(u) \cup \{u\}</tex> | ||
| + | UpdateVertex(s); | ||
| + | |||
| + | '''Main'''(): | ||
| + | <tex>s_{last} = s_{start}</tex> | ||
| + | '''Initialize'''(); | ||
| + | '''ComputeShortestPath'''(); | ||
| + | while (<tex>s_{start} \ne s_{goal}</tex>) | ||
| + | // if (<tex>g(s_{start}) = \infty</tex>) тогда путь на данной итерации не найден. | ||
| + | <tex>s_{start}</tex> = такая вершина s', что <tex>min_{s' \in Succ(s_{start})}(c(s_{start}, s') + g(s'))</tex> | ||
| + | Передвинулись вдоль найденного пути и изменили вершину <tex>s_{start}</tex>; | ||
| + | Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним. | ||
| + | if (граф изменился) | ||
| + | <tex>K_m = K_m + h(s_{last}, h_{start})</tex>; | ||
| + | <tex>s_{last} = s_{start}</tex>; | ||
| + | for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами: | ||
| + | Обновляем результат функции <tex>c(u; v)</tex>; | ||
| + | '''UpdateVertex'''(u); | ||
| + | ComputeShortestPath(); | ||
==Ссылки== | ==Ссылки== | ||
Версия 19:18, 4 января 2014
Алгоритм D* — алгоритм поиска кратчайшего пути во взвешенном ориентированном графе, где структура графа неизвестна заранее или постоянно подвергается изменению. Разработан Свеном Кёнигом и Максимом Лихачевым в 2002 году.
Содержание
Алгоритм LPA*
Постановка задачи
Дан взвешенный ориентированный граф . Даны вершины и . Требуется после каждого изменения графа уметь вычислять функцию для каждой известной вершины
Описание
Функция будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины до .
Будем поддерживать для каждой вершины два вида смежных с ней вершин:
- Обозначим множество как множество вершин, исходящих из вершины .
- Обозначим множество как множество вершин, входящих в вершину .
Ясно, что обязано соблюдаться условие: и .
Функция будет возвращать стоимость перехода из вершины в вершину . При этом .
Теперь опишем функцию . Эта функция будет использовать минимальные расстояние из минимальных расстояний от до вершин, входящих в данную вершины . Потенциально это и будет нам давать информацию о расстояние от до .
Вершина может быть 3-х видов:
- насыщена, если
- переполнена, если
- ненасыщена, если
Очевидно, что если все вершины насыщены, то мы можем найти расстояние от стартовой вершины до любой. Такой граф будем называть устойчивым (насыщенным).
Эвристическая функция теперь должна быть неотрицательная и выполнять неравенство треугольника, т.е. и для всех и
Функция , где - вершина, возвращает вектор из 2-ух значений , .
- .
- .
Если в конце поиска пути , то мы не смогли найти путь от до на текущей итерации. Но после следующего изменения графа путь вполне может найтись.
Псевдокод
Основная функция, описывающая алгоритм
Main():
{
Initialize();
while (true)
{
ComputeShortestPath();
В данный момент мы знаем кратчайший путь из в .
Ждем каких-либо изменений графа.
for всех ориентированных ребер с измененными весами:
{
Обновляем результат функции ;
UpdateVertex();
}
}
}
Теперь опишем составные элементы подробнее Функция инициализации исходного графа устанавливает для всех вершин кроме стартовой () значения и равными бесконечности. Для стартовой . Очевидно, что минимальное расстояние от стартовой вершины до самой себя должно быть равным 0, но . Это сделано для того, чтобы стартовая вершина была ненасыщенной и имела право попасть в приоритетную очередь.
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) хранит минимальное известное расстояние от до . Свойства остаются прежними.
Эвристическая функция теперь должна быть неотрицательная и обратно-устойчивая, т.е. и для всех и . Очевидно, что при движении робота изменяется, поэтому данные свойства должны выполняться для всех .
Дополнительное условие выхода также меняется, т.е. при путь не найден на данной итерации. Иначе путь найден и робот может проследовать по нему.
Примечание: Так же следует отметить, что функция 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 раз, если она переполнена. |
Описание (2 версия)
В первой версии алгоритма была серьезная проблема в том, что для каждой вершины в приоритетной очереди нужно было обновлять ключ суммарно за . Это дорогая операция, так как очередь может содержать огромное число вершин. Воспользуемся оригинальным методом поиска и изменим преобразим основной цикл, чтобы избежать постоянного перестроения очереди .
Теперь эвристическая функция должна поддерживать неравенство треугольника для всех вершин , т.е. . Так же должно выполняться свойство , где - стоимость перехода по кратчайшему пути из в , при этом и не должны быть обязательно смежными. Такие свойства не противоречат свойствами из первой версии, а лишь усиливают их.
Псевдокод (Вторая версия)
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 ( < CalcKey()) U.Insert(;CalcKey()); 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); ComputeShortestPath();