Алгоритм D* — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Описание)
(changed names)
Строка 4: Строка 4:
  
 
=== Постановка задачи ===
 
=== Постановка задачи ===
Дан [[Основные определения теории графов|взвешенный ориентированный граф]] <tex> G(V, E) </tex>. Даны вершины <tex>s_{start}</tex> и <tex>s_{goal}</tex>. Требуется после каждого изменения графа <tex>G</tex> уметь вычислять функцию <tex>g(s)</tex> для каждой известной вершины <tex>s \in V</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>s_{start}</tex> до <tex>s</tex>. Её значение будет почти аналогичным значению в [[Алгоритм A* | алгоритме A*]], за исключением того, что в данном алгоритме наc интересуют только <tex>g(s)</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>s_{start}</tex> до <tex>s</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 = s_{start} \\
+
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>s_{start}</tex> до вершин, входящих в данную вершину <tex>s</tex>, это будет нам давать информацию об оценочном расстоянии от <tex>s_{start}</tex> до <tex>s</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(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>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, s_{goal})</tex>  
+
* <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(s_{goal}) = +\infty</tex>, то мы не смогли найти путь от <tex>s_{start}</tex> до <tex>s_{goal}</tex> на текущей итерации. Но после следующего изменения графа путь вполне может найтись.
+
Если в конце поиска пути <tex>g(t) = +\infty</tex>, то мы не смогли найти путь от <tex>f</tex> до <tex>t</tex> на текущей итерации. Но после следующего изменения графа путь вполне может найтись.
  
 
=== Псевдокод ===
 
=== Псевдокод ===
Строка 58: Строка 58:
 
     while (true)
 
     while (true)
 
       '''ComputeShortestPath'''();
 
       '''ComputeShortestPath'''();
       //В данный момент мы знаем кратчайший путь из <tex>s_{start}</tex> в <tex>s_{goal}</tex>.
+
       //В данный момент мы знаем кратчайший путь из <tex>f</tex> в <tex>t</tex>.
 
       Ждем каких-либо изменений графа.
 
       Ждем каких-либо изменений графа.
 
       for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами:
 
       for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами:
Строка 65: Строка 65:
  
 
Теперь опишем составные элементы подробнее
 
Теперь опишем составные элементы подробнее
Функция инициализации исходного графа устанавливает для всех вершин кроме стартовой (<tex>s_{start}</tex>) значения <tex>g(s)</tex> и <tex>rhs(s)</tex> равными бесконечности. Для стартовой <tex>rhs(s_{start})=0</tex>. Очевидно, что минимальное расстояние от стартовой вершины до самой себя должно быть равным 0, но <tex>g(s_{start})=+\infty</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(s_{start}) = 0;</tex>
+
     <tex>rhs(f) = 0;</tex>
     U.Insert(<tex>s_{start}</tex>; CalcKey(<tex>s_{start}</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; s_{goal})</tex>; <tex>\min(g(s); rhs(s))</tex>];
+
     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 s_{start}</tex>  
+
     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> будем называть ''расширением'' вершины.
+
   // Функция несколько раз перерасчитывает значение <tex>g(s)</tex> у ненасыщенных вершин в неубывающем порядке их ключей. Такой перерасчет значения <tex>g(s)</tex> будем называть ''расширением'' вершины.
 
   '''ComputeShortestPath'''():
 
   '''ComputeShortestPath'''():
     while (U.TopKey() < CalcKey(<tex>s_{goal}</tex>) OR rhs(<tex>s_{goal}</tex>) <tex>\ne</tex> g(<tex>s_{goal}</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>s_{start}</tex> и <tex>s_{goal}</tex>, используя при этом данные из предыдущих итераций. Очевидно, что в худшем случае (а именно когда все ребра вокруг текущей вершины изменили свой вес) алгоритм будет работать как последовательные вызовы алгоритма А* за <tex>O(n^2 \cdot log(n))</tex>. Улучшим эту оценку с помощью алгоритма D* lite.
+
Таким образом мы описали алгоритм 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>s_{start}</tex> и <tex>s_{goal}</tex>. Требуется в процессе движения по кратчайшему пути в графе <tex>G</tex> обновлять значения функции <tex>g(s)</tex> при поступлении новой информации о графе <tex>G</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>s_{start}</tex>, в которой, допустим, находится способный к сканированию местности "робот", и конечной вершиной <tex>s_{goal}</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>s_{goal}</tex> до <tex>s</tex>. Свойства остаются прежними.
+
Теперь функция g(s) хранит минимальное известное расстояние от <tex>t</tex> до <tex>s</tex>. Свойства остаются прежними.
  
 
[[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и обратно-устойчивая, т.е.  
 
[[Эвристики для поиска кратчайших путей|Эвристическая функция]] <tex>h(s,s')</tex> теперь должна быть неотрицательная и обратно-устойчивая, т.е.  
<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>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(s_{start}) = +\infty</tex> путь не найден на данной итерации. Иначе путь найден и "робот" может проследовать по нему.
+
Дополнительное условие выхода также меняется, т.е. при <tex>g(f) = +\infty</tex> путь не найден на данной итерации. Иначе путь найден и "робот" может проследовать по нему.
  
 
'''Примечание''':  Так же следует отметить, что функция '''Initialize''' не обязана инициализировать абсолютно все вершины перед стартом алгоритма. Это важно, так как на практике число вершин может быть огромным, и только немногие будут пройдены роботом в процессе движения. Так же это дает возможность добавления/удаления ребер без потери устойчивости всех подграфов данного графа.
 
'''Примечание''':  Так же следует отметить, что функция '''Initialize''' не обязана инициализировать абсолютно все вершины перед стартом алгоритма. Это важно, так как на практике число вершин может быть огромным, и только немногие будут пройдены роботом в процессе движения. Так же это дает возможность добавления/удаления ребер без потери устойчивости всех подграфов данного графа.
Строка 134: Строка 134:
  
 
   '''CalcKey'''(s):
 
   '''CalcKey'''(s):
     return [<tex>\min(g(s);rhs(s)) + h(s_{start};s)</tex>;<tex>\min(g(s); rhs(s))</tex>];
+
     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(s_{goal}) = 0</tex>
+
     <tex>rhs(t) = 0</tex>
     U.Insert(<tex>s_{goal}</tex>; CalcKey(<tex>s_{goal}</tex>));
+
     U.Insert(<tex>t</tex>; CalcKey(<tex>t</tex>));
  
 
   '''UpdateVertex'''(u):
 
   '''UpdateVertex'''(u):
     if (<tex>u \ne s_{goal}</tex>)  
+
     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>s_{start}</tex>) OR <tex>rhs(s_{start}) \ne g(s_{start})</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>s_{start} \ne s_{goal}</tex>)
+
     while (<tex>f \ne t</tex>)
       // if (<tex>g(s_{start}) = \infty</tex>) тогда путь на данной итерации не найден.
+
       // if (<tex>g(f) = \infty</tex>) тогда путь на данной итерации не найден.
       <tex>s_{start}</tex> = такая вершина s', что <tex>\min\limits_{s' \in Succ(s_{start})}(c(s_{start}, s') + g(s'))</tex>
+
       <tex>f</tex> = такая вершина s', что <tex>\min\limits_{s' \in Succ(f)}(c(f, s') + g(s'))</tex>
       Передвинулись вдоль найденного пути и изменили вершину <tex>s_{start}</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(s_{start};s) + K_m</tex>;<tex>\min(g(s); rhs(s))</tex>];
+
     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(s_{goal}) = 0</tex>
+
     <tex>rhs(t) = 0</tex>
     U.Insert(<tex>s_{goal}</tex>; CalcKey(<tex>s_{goal}</tex>));
+
     U.Insert(<tex>t</tex>; CalcKey(<tex>t</tex>));
  
 
   '''UpdateVertex'''(u):
 
   '''UpdateVertex'''(u):
     if (<tex>u \ne s_{goal}</tex>)  
+
     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>s_{start}</tex>) OR <tex>rhs(s_{start}) \ne g(s_{start})</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} = s_{start}</tex>
+
     <tex>s_{last} = f</tex>
 
     '''Initialize'''();
 
     '''Initialize'''();
 
     '''ComputeShortestPath'''();
 
     '''ComputeShortestPath'''();
     while (<tex>s_{start} \ne s_{goal}</tex>)
+
     while (<tex>f \ne t</tex>)
       // if (<tex>g(s_{start}) = \infty</tex>) тогда путь на данной итерации не найден.
+
       // if (<tex>g(f) = \infty</tex>) тогда путь на данной итерации не найден.
       <tex>s_{start}</tex> = такая вершина s', что <tex>\min\limits_{s' \in Succ(s_{start})}(c(s_{start}, s') + g(s'))</tex>
+
       <tex>f</tex> = такая вершина s', что <tex>\min\limits_{s' \in Succ(f)}(c(f, s') + g(s'))</tex>
       Передвинулись вдоль найденного пути и изменили вершину <tex>s_{start}</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} = s_{start}</tex>;
+
         <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|350px]] || style="width:50%;text-align:center;" | [[Файл:Схема_движения_робота_Dstarv2_2.png|350px]]
+
  | 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*

Постановка задачи

Дан взвешенный ориентированный граф [math] G(V, E) [/math]. Даны вершины [math]f[/math] и [math]t[/math]. Требуется после каждого изменения графа [math]G[/math] уметь вычислять функцию [math]g(s)[/math] для каждой известной вершины [math]s \in V[/math]

Описание

Функция [math]g(s)[/math] будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины [math]f[/math] до [math]s[/math]. Её значение будет почти аналогичным значению в алгоритме A*, за исключением того, что в данном алгоритме наc интересуют только [math]g(s)[/math]-значения известных вершин на данной итерации.

Будем поддерживать для каждой вершины два вида смежных с ней вершин:

  • Обозначим множество [math]Succ(s) \subseteq V[/math] как множество вершин, исходящих из вершины [math]s[/math].
  • Обозначим множество [math]Pred(s) \subseteq V[/math] как множество вершин, входящих в вершину [math]s[/math].

Функция [math]0 \leqslant c(s, s') \leqslant +\infty[/math] будет возвращать стоимость перехода из вершины [math]s[/math] в вершину [math]s'[/math]. При этом [math]s' \in Succ(s)[/math].


Определение:
Будем называть rhs-значением (right-hand side value) такую функцию [math]rhs(s)[/math], которая будет возвращать потенциальное минимальное расстояние от [math]f[/math] до [math]s[/math] по следующим правилам:

[math]rhs(s) = \begin{cases} 0,& \text{if } s = f \\ \min\limits_{s' \in Pred(s)}(g(s') + c(s', s),& \text{otherwise} \end{cases} [/math]

Так как rhs-значение использует минимальное значение из минимальных расстояний от [math]f[/math] до вершин, входящих в данную вершину [math]s[/math], это будет нам давать информацию об оценочном расстоянии от [math]f[/math] до [math]s[/math].


Определение:
Вершина [math]s[/math] называется насыщенной (locally consistent), если [math]g(s) = rhs(s)[/math]


Определение:
Вершина [math]s[/math] называется переполненной (locally overconsistent), если [math]g(s) \gt rhs(s)[/math]


Определение:
Вершина [math]s[/math] называется ненасыщенной (locally underconsistent), если [math]g(s) \lt rhs(s)[/math]


Очевидно, что если все вершины насыщены, то мы можем найти расстояние от стартовой вершины до любой. Такой граф будем называть устойчивым (насыщенным).

Эвристическая функция [math]h(s,s')[/math] теперь должна быть неотрицательная и выполнять неравенство треугольника, т.е. [math]h(t,t) = 0[/math] и [math]h(s, t) \leqslant c(s,s') + h(s',t)[/math] для всех [math]s \in V[/math] и [math]s' \in Succ(s)[/math]


Определение:
Будем называть ключом вершины такую функцию [math]key(s)[/math], которая возвращает вектор из 2-ух значений [math]k_1(s)[/math], [math]k_2(s)[/math].
  • [math]k_1(s) = \min(g(s), rhs(s)) + h(s, t)[/math]
  • [math]k_2(s) = \min(g(s), rhs(s))[/math],
где [math]s[/math] - вершина из множества [math]V[/math]

Если в конце поиска пути [math]g(t) = +\infty[/math], то мы не смогли найти путь от [math]f[/math] до [math]t[/math] на текущей итерации. Но после следующего изменения графа путь вполне может найтись.

Псевдокод

Основная функция, описывающая алгоритм

 Main():
   Initialize();
   while (true)
     ComputeShortestPath();
     //В данный момент мы знаем кратчайший путь из [math]f[/math] в [math]t[/math].
     Ждем каких-либо изменений графа.
     for всех ориентированных ребер [math](u; v)[/math] с измененными весами:
       Обновляем результат функции [math]c(u; v)[/math];
       UpdateVertex([math]v[/math]);

Теперь опишем составные элементы подробнее Функция инициализации исходного графа устанавливает для всех вершин кроме стартовой ([math]f[/math]) значения [math]g(s)[/math] и [math]rhs(s)[/math] равными бесконечности. Для стартовой [math]rhs(f)=0[/math]. Очевидно, что минимальное расстояние от стартовой вершины до самой себя должно быть равным 0, но [math]g(f)=+\infty[/math]. Это сделано для того, чтобы стартовая вершина была ненасыщенной и имела право попасть в приоритетную очередь.

 Initialize():
   //Заведем приоритетную очередь [math]U[/math], в которую будем помещать вершины. Сортировка будет производиться по функции [math]key(s)[/math].
   [math]U = \varnothing;[/math]
   for [math]s \in S[/math]
     [math]rhs(s) = g(s) = \infty;[/math]
   [math]rhs(f) = 0;[/math]
   U.Insert([math]f[/math]; CalcKey([math]f[/math]));


 //Функция [math]key(s)[/math]. Возвращаемые значения сортируются в лексографическом порядке, т.е. сначала [math]k_1(s)[/math], потом [math]k_2(s)[/math]
 CalcKey(s):
   return [[math]\min(g(s); rhs(s)) + h(s; t)[/math]; [math]\min(g(s); rhs(s))[/math]];
 UpdateVertex([math]u[/math]):
   if [math]u \ne f[/math] 
     [math]rhs(u) = \min\limits_{s' \in Pred(u)}(g(s') + c(s',u));[/math]
   if [math]u \in U[/math]
     U.Remove(u);
   if [math]g(u) \ne rhs(u)[/math]
     U.Insert([math]u[/math]; CalcKey([math]u[/math]));
 // Функция несколько раз перерасчитывает значение [math]g(s)[/math] у ненасыщенных вершин в неубывающем порядке их ключей. Такой перерасчет значения [math]g(s)[/math] будем называть расширением вершины.
 ComputeShortestPath():
   while (U.TopKey() < CalcKey([math]t[/math]) OR rhs([math]t[/math]) [math]\ne[/math] g([math]t[/math]))
     u = U.Pop();
     if g(u) > rhs(u)
       g(u) = rhs(u);
       for [math]s \in Succ(u)[/math]
         UpdateVertex(s);
     else
       g(u) = [math]+\infty[/math];
       for [math]s \in Succ(u) \cup \{u\}[/math]
         UpdateVertex(s);

Таким образом мы описали алгоритм LPA*. Он неоднократно определяет путь между вершинами [math]f[/math] и [math]t[/math], используя при этом данные из предыдущих итераций. Очевидно, что в худшем случае (а именно когда все ребра вокруг текущей вершины изменили свой вес) алгоритм будет работать как последовательные вызовы алгоритма А* за [math]O(n^2 \cdot \log(n))[/math]. Улучшим эту оценку с помощью алгоритма D* lite.

Примечание: на практике же такой подход тоже имеет место на плотных графах (или матрицах), так как в среднем дает оценку [math]O(n \cdot \log(n))[/math].

Алгоритм D* (Первая версия)

Пока что был описан только алгоритм LPA*. Он способен неоднократно определять кратчайшее расстояние между начальной и конечной вершинами при любом изменении данного графа. Его первоначальный поиск полностью совпадает с алгоритмом A*, но последующие итерации способны использовать информацию из предыдущих поисков.

Постановка задачи

Дан взвешенный ориентированный граф [math] G(V, E) [/math]. Даны вершины [math]f[/math] и [math]t[/math]. Требуется в процессе движения по кратчайшему пути в графе [math]G[/math] обновлять значения функции [math]g(s)[/math] при поступлении новой информации о графе [math]G[/math].

Теперь на основе LPA* опишем алгоритм D*, который способен определять расстояние между текущей вершиной [math]f[/math], в которой, допустим, находится способный к сканированию местности "робот", и конечной вершиной [math]t[/math] при каждом изменении графа в то время, как наш "робот" движется вдоль найденного пути.

Схема движения "робота" в процессе работы алгоритма D*. Информация о серых клетках ему неизвестна до определенной итерации.

Описание

Опишем первую версию алгоритма D*. Очевидно, что большинство вершин в процессе движения робота остаются неизменными, поэтому мы можем применить алгоритм LPA*.

Примечание: Большинство функций переходят в данный алгоритм без изменений, поэтому опишем только измененные части.

Для начала мы поменяем направление поиска в графе.

Теперь функция g(s) хранит минимальное известное расстояние от [math]t[/math] до [math]s[/math]. Свойства остаются прежними.

Эвристическая функция [math]h(s,s')[/math] теперь должна быть неотрицательная и обратно-устойчивая, т.е. [math]h(f,f) = 0[/math] и [math]h(f, s) \leqslant h(f,s') + c(s',s)[/math] для всех [math]s \in S[/math] и [math]s' \in Pred(s)[/math]. Очевидно, что при движении робота [math]f[/math] изменяется, поэтому данные свойства должны выполняться для всех [math]f \in V[/math].

Дополнительное условие выхода также меняется, т.е. при [math]g(f) = +\infty[/math] путь не найден на данной итерации. Иначе путь найден и "робот" может проследовать по нему.

Примечание: Так же следует отметить, что функция Initialize не обязана инициализировать абсолютно все вершины перед стартом алгоритма. Это важно, так как на практике число вершин может быть огромным, и только немногие будут пройдены роботом в процессе движения. Так же это дает возможность добавления/удаления ребер без потери устойчивости всех подграфов данного графа.

Псевдокод

При такой постановке задачи псевдокод не сильно меняется, но функция Main все-таки претерпевает значительные изменения.

 CalcKey(s):
   return [[math]\min(g(s);rhs(s)) + h(f;s)[/math];[math]\min(g(s); rhs(s))[/math]];
 Initialize():
   U = [math]\varnothing[/math];
   for [math]s \in S[/math]
     [math]rhs(s) = g(s) = +\infty[/math]
   [math]rhs(t) = 0[/math]
   U.Insert([math]t[/math]; CalcKey([math]t[/math]));
 UpdateVertex(u):
   if ([math]u \ne t[/math]) 
     rhs(u) = [math]\min\limits_{s' \in Succ(u)}(c(u,s')+g(s'));[/math]
   if ([math]u \in U[/math]) 
     U.Remove(u);
   if ([math]g(u) \ne rhs(u)[/math]) 
     U.Insert(u; CalcKey(u));
 ComputeShortestPath():
   while (U.TopKey() < CalcKey([math]f[/math]) OR [math]rhs(f) \ne g(f)[/math])
     u = U.Pop();
     if (g(u) > rhs(u))
       g(u) = rhs(u);
       for [math]s \in Pred(u)[/math] 
         UpdateVertex(s);
     else
       g(u) = [math]+\infty[/math];
       for [math]s \in Pred(u) \cup \{u\}[/math] 
         UpdateVertex(s);
 Main():
   Initialize();
   ComputeShortestPath();
   while ([math]f \ne t[/math])
     // if ([math]g(f) = \infty[/math]) тогда путь на данной итерации не найден.
     [math]f[/math] = такая вершина s', что [math]\min\limits_{s' \in Succ(f)}(c(f, s') + g(s'))[/math]
     Передвинулись вдоль найденного пути и изменили вершину [math]f[/math];
     Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним.
     if (граф изменился)
       for всех ориентированных ребер [math](u; v)[/math] с измененными весами:
         Обновляем результат функции [math]c(u; v)[/math];
         UpdateVertex(u);
       for [math]s \in U[/math]
         U.Update([math]s[/math]; CalcKey([math]s[/math]));
       ComputeShortestPath();
Теорема (Свен Кёниг, Об устойчивой насыщенности вершин):
Функция ComputeShortestPath в данной версии алгоритма расширяет вершину максимум 2 раза, а именно 1 раз, если вершина ненасыщена, и максимум 1 раз, если она переполнена.

Алгоритм D* (Вторая версия)

Описание

В первой версии алгоритма была серьезная проблема в том, что для каждой вершины в приоритетной очереди нужно было обновлять ключ суммарно за [math]O(n \cdot \log(n))[/math]. Это дорогая операция, так как очередь может содержать огромное число вершин. Воспользуемся оригинальным методом поиска и изменим основной цикл, чтобы избежать постоянного перестроения очереди [math]U[/math].

Теперь эвристическая функция должна поддерживать неравенство треугольника для всех вершин [math]s,s',s'' \in V[/math], т.е. [math]h(s,s'') \leqslant h(s, s') + h(s',s'')[/math]. Так же должно выполняться свойство [math]h(s,s') \leqslant c^*(s,s')[/math], где [math]c^*(s,s')[/math] - стоимость перехода по кратчайшему пути из [math]s[/math] в [math]s'[/math], при этом [math]s[/math] и [math]s'[/math] не должны быть обязательно смежными. Такие свойства не противоречат свойствами из первой версии, а лишь усиливают их.

Псевдокод

 CalcKey(s):
   return [[math]\min(g(s);rhs(s)) + h(f;s) + K_m[/math];[math]\min(g(s); rhs(s))[/math]];
 Initialize():
   U = [math]\varnothing[/math];
   [math]K_m = 0[/math]
   for [math]s \in S[/math]
     [math]rhs(s) = g(s) = +\infty[/math]
   [math]rhs(t) = 0[/math]
   U.Insert([math]t[/math]; CalcKey([math]t[/math]));
 UpdateVertex(u):
   if ([math]u \ne t[/math]) 
     rhs(u) = [math]\min\limits_{s' \in Succ(u)}(c(u,s')+g(s'));[/math]
   if ([math]u \in U[/math]) 
     U.Remove(u);
   if ([math]g(u) \ne rhs(u)[/math]) 
     U.Insert(u; CalcKey(u));
 ComputeShortestPath():
   while (U.TopKey() < CalcKey([math]f[/math]) OR [math]rhs(f) \ne g(f)[/math])
     [math]K_{old} = U.TopKey()[/math];
     u = U.Pop();
     
     if ([math]K_{old}[/math] < CalcKey([math]u[/math]))
       U.Insert([math]u[/math]; CalcKey([math]u[/math]));
     
     if (g(u) > rhs(u))
       g(u) = rhs(u);
       for [math]s \in Pred(u)[/math] 
         UpdateVertex(s);
     else
       g(u) = [math]+\infty[/math];
       for [math]s \in Pred(u) \cup \{u\}[/math] 
         UpdateVertex(s);
 Main():
   [math]s_{last} = f[/math]
   Initialize();
   ComputeShortestPath();
   while ([math]f \ne t[/math])
     // if ([math]g(f) = \infty[/math]) тогда путь на данной итерации не найден.
     [math]f[/math] = такая вершина s', что [math]\min\limits_{s' \in Succ(f)}(c(f, s') + g(s'))[/math]
     Передвинулись вдоль найденного пути и изменили вершину [math]f[/math];
     Сканируем роботом какие-либо изменения в графе или убеждаемся, что граф остается прежним.
     if (граф изменился)
       [math]K_m = K_m + h(s_{last}, h_{start})[/math];
       [math]s_{last} = f[/math];
       for всех ориентированных ребер [math](u; v)[/math] с измененными весами:
         Обновляем результат функции [math]c(u; v)[/math];
         UpdateVertex(u);
       ComputeShortestPath();

Пример работы

Схема движения робота Dstarv2 1.png Схема движения робота Dstarv2 2.png
Итерации в функции ComputeShortestPath на исходном графе. Итерации в функции ComputeShortestPath после изменения графа. (Второй вызов функции)

Ссылки