Алгоритм D* — различия между версиями
(→Описание) |
Kabanov (обсуждение | вклад) (changed names) |
||
| Строка 4: | Строка 4: | ||
=== Постановка задачи === | === Постановка задачи === | ||
| − | Дан [[Основные определения теории графов|взвешенный ориентированный граф]] <tex> G(V, E) </tex>. Даны вершины <tex> | + | Дан [[Основные определения теории графов|взвешенный ориентированный граф]] <tex> G(V, E) </tex>. Даны вершины <tex>f</tex> и <tex>t</tex>. Требуется после каждого изменения графа <tex>G</tex> уметь вычислять функцию <tex>g(s)</tex> для каждой известной вершины <tex>s \in V</tex> |
=== Описание === | === Описание === | ||
| − | Функция <tex>g(s)</tex> будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины <tex> | + | Функция <tex>g(s)</tex> будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины <tex>f</tex> до <tex>s</tex>. Её значение будет почти аналогичным значению в [[Алгоритм A* | алгоритме A*]], за исключением того, что в данном алгоритме наc интересуют только <tex>g(s)</tex>-значения известных вершин на данной итерации. |
Будем поддерживать для каждой вершины два вида смежных с ней вершин: | Будем поддерживать для каждой вершины два вида смежных с ней вершин: | ||
| Строка 16: | Строка 16: | ||
{{Определение | {{Определение | ||
| − | |definition=Будем называть '''rhs-значением''' (''right-hand side value'') такую функцию <tex>rhs(s)</tex>, которая будет возвращать потенциальное минимальное расстояние от <tex> | + | |definition=Будем называть '''rhs-значением''' (''right-hand side value'') такую функцию <tex>rhs(s)</tex>, которая будет возвращать потенциальное минимальное расстояние от <tex>f</tex> до <tex>s</tex> по следующим правилам: |
<tex>rhs(s) = | <tex>rhs(s) = | ||
\begin{cases} | \begin{cases} | ||
| − | 0,& \text{if } s = | + | 0,& \text{if } s = f \\ |
\min\limits_{s' \in Pred(s)}(g(s') + c(s', s),& \text{otherwise} | \min\limits_{s' \in Pred(s)}(g(s') + c(s', s),& \text{otherwise} | ||
\end{cases} | \end{cases} | ||
</tex> | </tex> | ||
| − | Так как rhs-значение использует минимальное значение из минимальных расстояний от <tex> | + | Так как rhs-значение использует минимальное значение из минимальных расстояний от <tex>f</tex> до вершин, входящих в данную вершину <tex>s</tex>, это будет нам давать информацию об оценочном расстоянии от <tex>f</tex> до <tex>s</tex>. |
}} | }} | ||
| Строка 41: | Строка 41: | ||
[[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и выполнять неравенство треугольника, т.е. | [[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и выполнять неравенство треугольника, т.е. | ||
| − | <tex>h( | + | <tex>h(t,t) = 0</tex> и <tex>h(s, t) \leqslant c(s,s') + h(s',t)</tex> для всех <tex>s \in V</tex> и <tex>s' \in Succ(s)</tex> |
{{Определение | {{Определение | ||
|definition=Будем называть '''ключом''' вершины такую функцию <tex>key(s)</tex>, которая возвращает вектор из 2-ух значений <tex>k_1(s)</tex>, <tex>k_2(s)</tex>. | |definition=Будем называть '''ключом''' вершины такую функцию <tex>key(s)</tex>, которая возвращает вектор из 2-ух значений <tex>k_1(s)</tex>, <tex>k_2(s)</tex>. | ||
| − | * <tex>k_1(s) = \min(g(s), rhs(s)) + h(s, | + | * <tex>k_1(s) = \min(g(s), rhs(s)) + h(s, t)</tex> |
* <tex>k_2(s) = \min(g(s), rhs(s))</tex>, | * <tex>k_2(s) = \min(g(s), rhs(s))</tex>, | ||
где <tex>s</tex> - вершина из множества <tex>V</tex> | где <tex>s</tex> - вершина из множества <tex>V</tex> | ||
}} | }} | ||
| − | Если в конце поиска пути <tex>g( | + | Если в конце поиска пути <tex>g(t) = +\infty</tex>, то мы не смогли найти путь от <tex>f</tex> до <tex>t</tex> на текущей итерации. Но после следующего изменения графа путь вполне может найтись. |
=== Псевдокод === | === Псевдокод === | ||
| Строка 58: | Строка 58: | ||
while (true) | while (true) | ||
'''ComputeShortestPath'''(); | '''ComputeShortestPath'''(); | ||
| − | //В данный момент мы знаем кратчайший путь из <tex> | + | //В данный момент мы знаем кратчайший путь из <tex>f</tex> в <tex>t</tex>. |
Ждем каких-либо изменений графа. | Ждем каких-либо изменений графа. | ||
for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами: | for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами: | ||
| Строка 65: | Строка 65: | ||
Теперь опишем составные элементы подробнее | Теперь опишем составные элементы подробнее | ||
| − | Функция инициализации исходного графа устанавливает для всех вершин кроме стартовой (<tex> | + | Функция инициализации исходного графа устанавливает для всех вершин кроме стартовой (<tex>f</tex>) значения <tex>g(s)</tex> и <tex>rhs(s)</tex> равными бесконечности. Для стартовой <tex>rhs(f)=0</tex>. Очевидно, что минимальное расстояние от стартовой вершины до самой себя должно быть равным 0, но <tex>g(f)=+\infty</tex>. Это сделано для того, чтобы стартовая вершина была ненасыщенной и имела право попасть в приоритетную очередь. |
'''Initialize'''(): | '''Initialize'''(): | ||
//Заведем [[Двоичная куча|приоритетную очередь]] <tex>U</tex>, в которую будем помещать вершины. Сортировка будет производиться по функции <tex>key(s)</tex>. | //Заведем [[Двоичная куча|приоритетную очередь]] <tex>U</tex>, в которую будем помещать вершины. Сортировка будет производиться по функции <tex>key(s)</tex>. | ||
| Строка 71: | Строка 71: | ||
for <tex>s \in S</tex> | for <tex>s \in S</tex> | ||
<tex>rhs(s) = g(s) = \infty;</tex> | <tex>rhs(s) = g(s) = \infty;</tex> | ||
| − | <tex>rhs( | + | <tex>rhs(f) = 0;</tex> |
| − | U.Insert(<tex> | + | U.Insert(<tex>f</tex>; CalcKey(<tex>f</tex>)); |
//Функция <tex>key(s)</tex>. Возвращаемые значения сортируются в лексографическом порядке, т.е. сначала <tex>k_1(s)</tex>, потом <tex>k_2(s)</tex> | //Функция <tex>key(s)</tex>. Возвращаемые значения сортируются в лексографическом порядке, т.е. сначала <tex>k_1(s)</tex>, потом <tex>k_2(s)</tex> | ||
'''CalcKey'''(s): | '''CalcKey'''(s): | ||
| − | return [<tex>\min(g(s); rhs(s)) + h(s; | + | return [<tex>\min(g(s); rhs(s)) + h(s; t)</tex>; <tex>\min(g(s); rhs(s))</tex>]; |
'''UpdateVertex'''(<tex>u</tex>): | '''UpdateVertex'''(<tex>u</tex>): | ||
| − | if <tex>u \ne | + | if <tex>u \ne f</tex> |
<tex>rhs(u) = \min\limits_{s' \in Pred(u)}(g(s') + c(s',u));</tex> | <tex>rhs(u) = \min\limits_{s' \in Pred(u)}(g(s') + c(s',u));</tex> | ||
if <tex>u \in U</tex> | if <tex>u \in U</tex> | ||
| Строка 87: | Строка 87: | ||
U.Insert(<tex>u</tex>; CalcKey(<tex>u</tex>)); | U.Insert(<tex>u</tex>; CalcKey(<tex>u</tex>)); | ||
| − | // Функция | + | // Функция несколько раз перерасчитывает значение <tex>g(s)</tex> у ненасыщенных вершин в неубывающем порядке их ключей. Такой перерасчет значения <tex>g(s)</tex> будем называть ''расширением'' вершины. |
'''ComputeShortestPath'''(): | '''ComputeShortestPath'''(): | ||
| − | while (U.TopKey() < CalcKey(<tex> | + | while (U.TopKey() < CalcKey(<tex>t</tex>) OR rhs(<tex>t</tex>) <tex>\ne</tex> g(<tex>t</tex>)) |
u = U.Pop(); | u = U.Pop(); | ||
if g(u) > rhs(u) | if g(u) > rhs(u) | ||
| Строка 100: | Строка 100: | ||
UpdateVertex(s); | UpdateVertex(s); | ||
| − | Таким образом мы описали алгоритм LPA*. Он неоднократно определяет путь между вершинами <tex> | + | Таким образом мы описали алгоритм LPA*. Он неоднократно определяет путь между вершинами <tex>f</tex> и <tex>t</tex>, используя при этом данные из предыдущих итераций. Очевидно, что в худшем случае (а именно когда все ребра вокруг текущей вершины изменили свой вес) алгоритм будет работать как последовательные вызовы алгоритма А* за <tex>O(n^2 \cdot \log(n))</tex>. Улучшим эту оценку с помощью алгоритма D* lite. |
| − | '''Примечание''': на практике же такой подход тоже имеет место на плотных графах (или матрицах), так как в среднем дает оценку <tex>O(n \cdot log(n))</tex>. | + | '''Примечание''': на практике же такой подход тоже имеет место на плотных графах (или матрицах), так как в среднем дает оценку <tex>O(n \cdot \log(n))</tex>. |
== Алгоритм D* (Первая версия) == | == Алгоритм D* (Первая версия) == | ||
| Строка 108: | Строка 108: | ||
=== Постановка задачи === | === Постановка задачи === | ||
| − | Дан [[Основные определения теории графов|взвешенный ориентированный граф]] <tex> G(V, E) </tex>. Даны вершины <tex> | + | Дан [[Основные определения теории графов|взвешенный ориентированный граф]] <tex> G(V, E) </tex>. Даны вершины <tex>f</tex> и <tex>t</tex>. Требуется в процессе движения по кратчайшему пути в графе <tex>G</tex> обновлять значения функции <tex>g(s)</tex> при поступлении новой информации о графе <tex>G</tex>. |
| − | Теперь на основе LPA* опишем алгоритм D*, который способен определять расстояние между текущей вершиной <tex> | + | Теперь на основе LPA* опишем алгоритм D*, который способен определять расстояние между текущей вершиной <tex>f</tex>, в которой, допустим, находится способный к сканированию местности "робот", и конечной вершиной <tex>t</tex> при каждом изменении графа в то время, как наш "робот" движется вдоль найденного пути. |
[[Файл:Схема_движения_робота_Dstar.png|350px|thumb|right|Схема движения "робота" в процессе работы алгоритма D*. Информация о серых клетках ему неизвестна до определенной итерации.]] | [[Файл:Схема_движения_робота_Dstar.png|350px|thumb|right|Схема движения "робота" в процессе работы алгоритма D*. Информация о серых клетках ему неизвестна до определенной итерации.]] | ||
| Строка 121: | Строка 121: | ||
Для начала мы поменяем направление поиска в графе. | Для начала мы поменяем направление поиска в графе. | ||
| − | Теперь функция g(s) хранит минимальное известное расстояние от <tex> | + | Теперь функция g(s) хранит минимальное известное расстояние от <tex>t</tex> до <tex>s</tex>. Свойства остаются прежними. |
[[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и обратно-устойчивая, т.е. | [[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и обратно-устойчивая, т.е. | ||
| − | <tex>h( | + | <tex>h(f,f) = 0</tex> и <tex>h(f, s) \leqslant h(f,s') + c(s',s)</tex> для всех <tex>s \in S</tex> и <tex>s' \in Pred(s)</tex>. Очевидно, что при движении робота <tex>f</tex> изменяется, поэтому данные свойства должны выполняться для всех <tex>f \in V</tex>. |
| − | Дополнительное условие выхода также меняется, т.е. при <tex>g( | + | Дополнительное условие выхода также меняется, т.е. при <tex>g(f) = +\infty</tex> путь не найден на данной итерации. Иначе путь найден и "робот" может проследовать по нему. |
'''Примечание''': Так же следует отметить, что функция '''Initialize''' не обязана инициализировать абсолютно все вершины перед стартом алгоритма. Это важно, так как на практике число вершин может быть огромным, и только немногие будут пройдены роботом в процессе движения. Так же это дает возможность добавления/удаления ребер без потери устойчивости всех подграфов данного графа. | '''Примечание''': Так же следует отметить, что функция '''Initialize''' не обязана инициализировать абсолютно все вершины перед стартом алгоритма. Это важно, так как на практике число вершин может быть огромным, и только немногие будут пройдены роботом в процессе движения. Так же это дает возможность добавления/удаления ребер без потери устойчивости всех подграфов данного графа. | ||
| Строка 134: | Строка 134: | ||
'''CalcKey'''(s): | '''CalcKey'''(s): | ||
| − | return [<tex>\min(g(s);rhs(s)) + h( | + | return [<tex>\min(g(s);rhs(s)) + h(f;s)</tex>;<tex>\min(g(s); rhs(s))</tex>]; |
'''Initialize'''(): | '''Initialize'''(): | ||
| Строка 140: | Строка 140: | ||
for <tex>s \in S</tex> | for <tex>s \in S</tex> | ||
<tex>rhs(s) = g(s) = +\infty</tex> | <tex>rhs(s) = g(s) = +\infty</tex> | ||
| − | <tex>rhs( | + | <tex>rhs(t) = 0</tex> |
| − | U.Insert(<tex> | + | U.Insert(<tex>t</tex>; CalcKey(<tex>t</tex>)); |
'''UpdateVertex'''(u): | '''UpdateVertex'''(u): | ||
| − | if (<tex>u \ne | + | if (<tex>u \ne t</tex>) |
rhs(u) = <tex>\min\limits_{s' \in Succ(u)}(c(u,s')+g(s'));</tex> | rhs(u) = <tex>\min\limits_{s' \in Succ(u)}(c(u,s')+g(s'));</tex> | ||
if (<tex>u \in U</tex>) | if (<tex>u \in U</tex>) | ||
| Строка 152: | Строка 152: | ||
'''ComputeShortestPath'''(): | '''ComputeShortestPath'''(): | ||
| − | while (U.TopKey() < CalcKey(<tex> | + | while (U.TopKey() < CalcKey(<tex>f</tex>) OR <tex>rhs(f) \ne g(f)</tex>) |
u = U.Pop(); | u = U.Pop(); | ||
if (g(u) > rhs(u)) | if (g(u) > rhs(u)) | ||
| Строка 166: | Строка 166: | ||
'''Initialize'''(); | '''Initialize'''(); | ||
'''ComputeShortestPath'''(); | '''ComputeShortestPath'''(); | ||
| − | while (<tex> | + | while (<tex>f \ne t</tex>) |
| − | // if (<tex>g( | + | // if (<tex>g(f) = \infty</tex>) тогда путь на данной итерации не найден. |
| − | <tex> | + | <tex>f</tex> = такая вершина s', что <tex>\min\limits_{s' \in Succ(f)}(c(f, s') + g(s'))</tex> |
| − | Передвинулись вдоль найденного пути и изменили вершину <tex> | + | Передвинулись вдоль найденного пути и изменили вершину <tex>f</tex>; |
Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним. | Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним. | ||
if (граф изменился) | if (граф изменился) | ||
| Строка 188: | Строка 188: | ||
=== Описание === | === Описание === | ||
| − | В первой версии алгоритма была серьезная проблема в том, что для каждой вершины в приоритетной очереди нужно было обновлять ключ суммарно за <tex>O(n \cdot log(n))</tex>. Это дорогая операция, так как очередь может содержать огромное число вершин. Воспользуемся оригинальным методом поиска и изменим основной цикл, чтобы избежать постоянного перестроения очереди <tex>U</tex>. | + | В первой версии алгоритма была серьезная проблема в том, что для каждой вершины в приоритетной очереди нужно было обновлять ключ суммарно за <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> не должны быть обязательно смежными. Такие свойства не противоречат свойствами из первой версии, а лишь усиливают их. | Теперь эвристическая функция должна поддерживать неравенство треугольника для всех вершин <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> не должны быть обязательно смежными. Такие свойства не противоречат свойствами из первой версии, а лишь усиливают их. | ||
| Строка 195: | Строка 195: | ||
'''CalcKey'''(s): | '''CalcKey'''(s): | ||
| − | return [<tex>\min(g(s);rhs(s)) + h( | + | return [<tex>\min(g(s);rhs(s)) + h(f;s) + K_m</tex>;<tex>\min(g(s); rhs(s))</tex>]; |
'''Initialize'''(): | '''Initialize'''(): | ||
| Строка 202: | Строка 202: | ||
for <tex>s \in S</tex> | for <tex>s \in S</tex> | ||
<tex>rhs(s) = g(s) = +\infty</tex> | <tex>rhs(s) = g(s) = +\infty</tex> | ||
| − | <tex>rhs( | + | <tex>rhs(t) = 0</tex> |
| − | U.Insert(<tex> | + | U.Insert(<tex>t</tex>; CalcKey(<tex>t</tex>)); |
'''UpdateVertex'''(u): | '''UpdateVertex'''(u): | ||
| − | if (<tex>u \ne | + | if (<tex>u \ne t</tex>) |
rhs(u) = <tex>\min\limits_{s' \in Succ(u)}(c(u,s')+g(s'));</tex> | rhs(u) = <tex>\min\limits_{s' \in Succ(u)}(c(u,s')+g(s'));</tex> | ||
if (<tex>u \in U</tex>) | if (<tex>u \in U</tex>) | ||
| Строка 214: | Строка 214: | ||
'''ComputeShortestPath'''(): | '''ComputeShortestPath'''(): | ||
| − | while (U.TopKey() < CalcKey(<tex> | + | while (U.TopKey() < CalcKey(<tex>f</tex>) OR <tex>rhs(f) \ne g(f)</tex>) |
<tex>K_{old} = U.TopKey()</tex>; | <tex>K_{old} = U.TopKey()</tex>; | ||
u = U.Pop(); | u = U.Pop(); | ||
| Строка 231: | Строка 231: | ||
'''Main'''(): | '''Main'''(): | ||
| − | <tex>s_{last} = | + | <tex>s_{last} = f</tex> |
'''Initialize'''(); | '''Initialize'''(); | ||
'''ComputeShortestPath'''(); | '''ComputeShortestPath'''(); | ||
| − | while (<tex> | + | while (<tex>f \ne t</tex>) |
| − | // if (<tex>g( | + | // if (<tex>g(f) = \infty</tex>) тогда путь на данной итерации не найден. |
| − | <tex> | + | <tex>f</tex> = такая вершина s', что <tex>\min\limits_{s' \in Succ(f)}(c(f, s') + g(s'))</tex> |
| − | Передвинулись вдоль найденного пути и изменили вершину <tex> | + | Передвинулись вдоль найденного пути и изменили вершину <tex>f</tex>; |
Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним. | Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним. | ||
if (граф изменился) | if (граф изменился) | ||
<tex>K_m = K_m + h(s_{last}, h_{start})</tex>; | <tex>K_m = K_m + h(s_{last}, h_{start})</tex>; | ||
| − | <tex>s_{last} = | + | <tex>s_{last} = f</tex>; |
for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами: | for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами: | ||
Обновляем результат функции <tex>c(u; v)</tex>; | Обновляем результат функции <tex>c(u; v)</tex>; | ||
| Строка 250: | Строка 250: | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
| − | | style="width:50%;text-align:center;" | [[Файл:Схема_движения_робота_Dstarv2_1.png| | + | | style="width:50%;text-align:center;" | [[Файл:Схема_движения_робота_Dstarv2_1.png|400px]] || style="width:50%;text-align:center;" | [[Файл:Схема_движения_робота_Dstarv2_2.png|400px]] |
|- | |- | ||
| style="width:50%;text-align:center;" | Итерации в функции '''ComputeShortestPath''' на исходном графе. || style="width:50%;text-align:center;" | Итерации в функции '''ComputeShortestPath''' после изменения графа. (Второй вызов функции) | | style="width:50%;text-align:center;" | Итерации в функции '''ComputeShortestPath''' на исходном графе. || style="width:50%;text-align:center;" | Итерации в функции '''ComputeShortestPath''' после изменения графа. (Второй вызов функции) | ||
Версия 18:41, 5 января 2014
Алгоритм D* — алгоритм поиска кратчайшего пути во взвешенном ориентированном графе, где структура графа неизвестна заранее или постоянно подвергается изменению. Разработан Свеном Кёнигом и Максимом Лихачевым в 2002 году.
Содержание
Алгоритм LPA*
Постановка задачи
Дан взвешенный ориентированный граф . Даны вершины и . Требуется после каждого изменения графа уметь вычислять функцию для каждой известной вершины
Описание
Функция будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины до . Её значение будет почти аналогичным значению в алгоритме A*, за исключением того, что в данном алгоритме наc интересуют только -значения известных вершин на данной итерации.
Будем поддерживать для каждой вершины два вида смежных с ней вершин:
- Обозначим множество как множество вершин, исходящих из вершины .
- Обозначим множество как множество вершин, входящих в вершину .
Функция будет возвращать стоимость перехода из вершины в вершину . При этом .
| Определение: |
| Будем называть rhs-значением (right-hand side value) такую функцию , которая будет возвращать потенциальное минимальное расстояние от до по следующим правилам:
Так как rhs-значение использует минимальное значение из минимальных расстояний от до вершин, входящих в данную вершину , это будет нам давать информацию об оценочном расстоянии от до . |
| Определение: |
| Вершина называется насыщенной (locally consistent), если |
| Определение: |
| Вершина называется переполненной (locally overconsistent), если |
| Определение: |
| Вершина называется ненасыщенной (locally underconsistent), если |
Очевидно, что если все вершины насыщены, то мы можем найти расстояние от стартовой вершины до любой. Такой граф будем называть устойчивым (насыщенным).
Эвристическая функция теперь должна быть неотрицательная и выполнять неравенство треугольника, т.е. и для всех и
| Определение: |
Будем называть ключом вершины такую функцию , которая возвращает вектор из 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() g()) 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 раз, если она переполнена. |
Алгоритм D* (Вторая версия)
Описание
В первой версии алгоритма была серьезная проблема в том, что для каждой вершины в приоритетной очереди нужно было обновлять ключ суммарно за . Это дорогая операция, так как очередь может содержать огромное число вершин. Воспользуемся оригинальным методом поиска и изменим основной цикл, чтобы избежать постоянного перестроения очереди .
Теперь эвристическая функция должна поддерживать неравенство треугольника для всех вершин , т.е. . Так же должно выполняться свойство , где - стоимость перехода по кратчайшему пути из в , при этом и не должны быть обязательно смежными. Такие свойства не противоречат свойствами из первой версии, а лишь усиливают их.
Псевдокод
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();
Пример работы
|
|
| Итерации в функции ComputeShortestPath на исходном графе. | Итерации в функции ComputeShortestPath после изменения графа. (Второй вызов функции) |

