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

Материал из Викиконспекты
Перейти к: навигация, поиск
м (Ссылки)
Строка 11: Строка 11:
 
Аналогично множество <tex>Pred(s) \in V</tex> как множество вершин, входящих в вершину <tex>s</tex>.  
 
Аналогично множество <tex>Pred(s) \in V</tex> как множество вершин, входящих в вершину <tex>s</tex>.  
  
Функция <tex>0 \le с(s, s') \le +\infty</tex> будет возвращать стоимость перехода из вершины <tex>s</tex> в вершину <tex>s'</tex>. При этом <tex>s' \in Succ(s)</tex>.
+
Функция <tex>0 \lessgtr с(s, s') \lessgtr +\infty</tex> будет возвращать стоимость перехода из вершины <tex>s</tex> в вершину <tex>s'</tex>. При этом <tex>s' \in Succ(s)</tex>.
  
 
Функция <tex>g(s)</tex> будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины <tex>s_{start}</tex> до <tex>s</tex>.
 
Функция <tex>g(s)</tex> будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины <tex>s_{start}</tex> до <tex>s</tex>.
Строка 27: Строка 27:
 
Очевидно, что если все вершины насыщены, то мы можем найти расстояние от стартовой вершины до любой.
 
Очевидно, что если все вершины насыщены, то мы можем найти расстояние от стартовой вершины до любой.
  
Функция <tex>key(s)</tex>, где <tex>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_2(s) = min(g(s), rhs(s))</tex>
+
Функция <tex>key(s)</tex>, где <tex>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_2(s) = min(g(s), rhs(s))</tex>.
  
 
Если в конце поиска пути <tex>g(s_{goal}) = +\infty</tex>, то мы не смогли найти путь от <tex>s_{start}</tex> до <tex>s_{goal}</tex> на текущей итерации. Но после следующего изменения графа путь вполне может найтись.
 
Если в конце поиска пути <tex>g(s_{goal}) = +\infty</tex>, то мы не смогли найти путь от <tex>s_{start}</tex> до <tex>s_{goal}</tex> на текущей итерации. Но после следующего изменения графа путь вполне может найтись.
Строка 65: Строка 67:
 
   '''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; s_{goal})</tex>; <tex>\min(g(s); rhs(s))</tex>];
 
   }
 
   }
  
Строка 92: Строка 94:
 
   }
 
   }
  
Таким образом мы описали алгоритм LPA*. Он неоднократно определяет путь между вершинами <tex>s_start</tex> и <tex>s_goal</tex>, используя при этом данные из предыдущих итераций. Очевидно, что в худшем случае (а именно когда все ребра вокруг текущей вершины изменили свой вес) алгоритм будет работать как последовательные вызовы алгоритма А* за <tex>O(n^2*log(n))</tex>. Улучшим эту оценку с помощью алгоритма D* lite.
+
Таким образом мы описали алгоритм LPA*. Он неоднократно определяет путь между вершинами <tex>s_{start}</tex> и <tex>s_{goal}</tex>, используя при этом данные из предыдущих итераций. Очевидно, что в худшем случае (а именно когда все ребра вокруг текущей вершины изменили свой вес) алгоритм будет работать как последовательные вызовы алгоритма А* за <tex>O(n^2 \dot log(n))</tex>. Улучшим эту оценку с помощью алгоритма D* lite.
  
 
'''Примечание''': на практике же такой подход тоже имеет место на плотных графах (или матрицах), так как в среднем дает оценку О(n*log(n)).
 
'''Примечание''': на практике же такой подход тоже имеет место на плотных графах (или матрицах), так как в среднем дает оценку О(n*log(n)).
Строка 117: Строка 119:
 
       U.Insert(u; CalcKey(u));
 
       U.Insert(u; CalcKey(u));
  
   '''ComputeShortestPath'''()
+
   '''ComputeShortestPath'''():
     while (U.TopKey() <tex><=</tex> CalcKey(<tex>s_{start}</tex>) OR <tex>rhs(s_{start}) != g(s_{start})</tex>)
+
     while (U.TopKey() < CalcKey(<tex>s_{start}</tex>) OR <tex>rhs(s_{start}) \ne g(s_{start})</tex>)
 
       u = U.Pop();
 
       u = U.Pop();
 
       if (g(u) > rhs(u))
 
       if (g(u) > rhs(u))
Строка 130: Строка 132:
  
 
   '''Main'''():
 
   '''Main'''():
     Initialize();
+
     '''Initialize'''();
     ComputeShortestPath();
+
     '''ComputeShortestPath'''();
 
     while (<tex>s_{start} \ne s_{goal}</tex>)
 
     while (<tex>s_{start} \ne s_{goal}</tex>)
 
       // if (<tex>g(s_{start}) = \infty</tex>) тогда путь на данной итерации не найден.
 
       // if (<tex>g(s_{start}) = \infty</tex>) тогда путь на данной итерации не найден.
       s_{start} = argmins02Succ(sstart)
+
       <tex>s_{start}</tex> = такая вершина s', что <tex>min_{s' \in Succ(s_{start})}(c(s_{start}, s') + g(s'))</tex>
       Move to <tex>s_{start}</tex>;
+
       Передвинулись вдоль найденного пути и изменили вершину <tex>s_{start}</tex>;
       Scan graph for changed edge costs;
+
       Ждем каких-либо изменений в графе или убеждаемся, что граф остается прежним.
       if any edge costs changed
+
       if (если граф изменился)
         for all directed edges (u; v) with changed edge costs
+
         for всех ориентированных ребер <tex>(u; v)</tex> с измененными весами:
           Update the edge cost c(u; v);
+
           Обновляем результат функции <tex>c(u; v)</tex>;
           UpdateVertex(u);
+
           '''UpdateVertex'''(u);
 
         for <tex>s \in U</tex>
 
         for <tex>s \in U</tex>
           U.Update(<tex>s</tex>; CalcKey(<tex>s</tex>));
+
           U.Update(<tex>s</tex>; '''CalcKey'''(<tex>s</tex>));
 
         ComputeShortestPath();
 
         ComputeShortestPath();
  

Версия 00:05, 3 января 2014

Алгоритм D* — алгоритм поиска кратчайшего пути во взвешенном ориентированном графе, где структура графа неизвестна заранее или постоянно подвергается изменению. Разработан Свеном Кёнигом и Максимом Лихачевым в 2002 году.

Алгоритм LPA*

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

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

Описание

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

Аналогично множество [math]Pred(s) \in V[/math] как множество вершин, входящих в вершину [math]s[/math].

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

Функция [math]g(s)[/math] будет возвращать последнее известное (и самое минимальное) значение расстояния от вершины [math]s_{start}[/math] до [math]s[/math].

Если [math]s = s_{start}[/math]

[math]rhs(s) = 0[/math]

Иначе

[math]rhs(s) = min_{s' \in pred(s)}(g(s') + c(s', s)[/math]

Вершина [math]s[/math] может быть 3-х видов:

  • насыщена, если [math]g(s) = rhs(s)[/math]
  • переполнена, если [math]g(s) \gt rhs(s)[/math]
  • ненасыщена, если [math]g(s) \lt rhs(s)[/math]

Очевидно, что если все вершины насыщены, то мы можем найти расстояние от стартовой вершины до любой.

Функция [math]key(s)[/math], где [math]s[/math] - вершина, возвращает вектор из 2-ух значений [math]k_1(s)[/math], [math]k_2(s)[/math].

  • [math]k_1(s) = min(g(s), rhs(s)) + h(s, s_goal)[/math].
  • [math]k_2(s) = min(g(s), rhs(s))[/math].

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

Псевдокод

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

 Main():
 {
   Initialize();
   while (true)
   {
     ComputeShortestPath();
     В данный момент мы знаем кратчайший путь из [math]s_{start}[/math] в [math]s_{goal}[/math].
     Ждем каких-либо изменений графа.
     for всех ориентированных ребер [math](u; v)[/math] с измененными весами:
     {
       Обновляем результат функции [math]c(u; v)[/math];
       UpdateVertex([math]v[/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(s_{start}) = 0;[/math]
   U.Insert([math]s_{start}[/math]; CalcKey([math]s_{start}[/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; s_{goal})[/math]; [math]\min(g(s); rhs(s))[/math]];
 }
 UpdateVertex([math]u[/math]):
 {
   if ([math]u \ne s_{start}[/math]) 
     [math]rhs(u) = min_{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]));
 }
 ComputeShortestPath():
 {
   while (U.TopKey() < CalcKey([math]s_{goal}[/math]) OR rhs([math]s_{goal}) \ne g(s_{goal}[/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]s_{start}[/math] и [math]s_{goal}[/math], используя при этом данные из предыдущих итераций. Очевидно, что в худшем случае (а именно когда все ребра вокруг текущей вершины изменили свой вес) алгоритм будет работать как последовательные вызовы алгоритма А* за [math]O(n^2 \dot log(n))[/math]. Улучшим эту оценку с помощью алгоритма D* lite.

Примечание: на практике же такой подход тоже имеет место на плотных графах (или матрицах), так как в среднем дает оценку О(n*log(n)).

Алгоритм D*

Псевдокод (Первая версия)

 CalcKey(s):
   return [[math]\min(g(s);rhs(s)) + h(s_{start};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(s_{goal}) = 0[/math]
   U.Insert([math]s_{goal}[/math]; CalcKey([math]s_{goal}[/math]));
 UpdateVertex(u):
   if ([math]u != s_{goal}[/math]) 
     rhs(u) = min_{s' \in Succ(u)}(c(u,s')+g(s'));
   if ([math]u \in U[/math]) 
     U.Remove(u);
   if ([math]g(u) != rhs(u)[/math]) 
     U.Insert(u; CalcKey(u));
 ComputeShortestPath():
   while (U.TopKey() < CalcKey([math]s_{start}[/math]) OR [math]rhs(s_{start}) \ne g(s_{start})[/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]s_{start} \ne s_{goal}[/math])
     // if ([math]g(s_{start}) = \infty[/math]) тогда путь на данной итерации не найден.
     [math]s_{start}[/math] = такая вершина s', что [math]min_{s' \in Succ(s_{start})}(c(s_{start}, s') + g(s'))[/math]
     Передвинулись вдоль найденного пути и изменили вершину [math]s_{start}[/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();

Ссылки