1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

# теория · шаг 1 из 6

A*: Дейкстра, которая знает, куда идти

Коротко

Дейкстра ищет во все стороны одинаково - ей всё равно, где цель. Если цель ИЗВЕСТНА и есть дешёвая оценка «сколько ещё осталось», перебор можно резко сократить. A* берёт из очереди вершину не с наименьшим g(v) (пройденным расстоянием), а с наименьшим f(v) = g(v) + h(v), где h(v) - эвристика, оценка остатка пути до цели. При h ≡ 0 это ровно Дейкстра; чем точнее h, тем меньше вершин будет раскрыто. И это не отдельный алгоритм, а та же Дейкстра с потенциалами из прошлой главы: эвристика играет роль потенциала, перевешивающего рёбра как w'(u, v) = w(u, v) + h(v) − h(u).

Если ты знаешь формулу f = g + h, понимаешь, что при h ≡ 0 получается Дейкстра, и видишь связь эвристики с потенциалами, листай до «Теперь сам».

Как думаешь, зачем искать во все стороны

Дейкстра из Москвы во Владивосток честно расходится кругом: она раскроет Петербург, Минск и Мурманск раньше, чем доберётся до Урала, - потому что они ближе. Прежде чем читать дальше, подумай: какую подсказку надо дать алгоритму, чтобы он предпочитал вершины «в сторону цели»? И почему эта подсказка не должна врать в большую сторону?

На что это похоже

Поиск дома в незнакомом городе. Можно обходить кварталы по кругу, расширяя радиус, - найдёшь обязательно, но долго. А можно пользоваться тем, что видно направление: идти по улицам, приближающим к цели, даже если формально это не ближайший перекрёсток. Компас не заменяет карту - он подсказывает, какой из равных вариантов пробовать первым.

Механизм: f = g + h

import heapq                                # та же куча, что у Дейкстры
                                            # adj и V - данные графа, заводятся снаружи

def a_star(start, goal, h):
    g = {start: 0}
    q = [(h(start), start)]                 # приоритет - ОЦЕНКА ПОЛНОГО пути
    while q:
        f, v = heapq.heappop(q)
        if v == goal:
            return g[v]                     # цель извлечена - ответ готов
        for u, w in adj[v]:
            ng = g[v] + w
            if u not in g or ng < g[u]:
                g[u] = ng
                heapq.heappush(q, (ng + h(u), u))
    return None

Три величины, которые важно не путать:

g(v) - НАСТОЯЩАЯ длина найденного пути от старта до v (то же, что dist у Дейкстры)
h(v) - ОЦЕНКА оставшегося пути от v до цели (эвристика, считается формулой, не поиском)
f(v) = g(v) + h(v) - оценка полной длины пути через v; по ней и выбирают

Предельные случаи стоит запомнить оба (знак ≡ читается «тождественно равно»: не «в этой точке совпало», а «равно нулю для каждой вершины»):

h ≡ 0                      → f = g → это в точности Дейкстра
h = настоящее расстояние   → раскрывается только то, что лежит на оптимальных путях
h слишком большое          → быстро, но ответ может оказаться не кратчайшим

Типичные эвристики: на сетке - манхэттенское расстояние (если ходить можно по четырём направлениям) или евклидово (если разрешены любые направления); на карте дорог - расстояние по прямой, делённое на максимальную скорость; в головоломках - число фишек не на своих местах или сумма их расстояний до мест.

Правило

A* выбирает из очереди вершину с наименьшим f(v) = g(v) + h(v), где g - пройденное расстояние, h - оценка остатка до цели. При h ≡ 0 это Дейкстра; чем точнее h, тем меньше раскрытых вершин. Цена операций та же, что у Дейкстры, - выигрыш в ЧИСЛЕ раскрытий, а не в цене одной. Эвристика - это потенциал: A* эквивалентен Дейкстре на графе с весами w'(u, v) = w(u, v) + h(v) − h(u).

Разбор: A* - это Дейкстра с потенциалами

Вернёмся к Джонсону. Там мы перевешивали рёбра как w'(u, v) = w(u, v) + h[u] − h[v] и получали задачу, у которой те же кратчайшие пути. Подставим в качестве потенциала эвристику: w'(u, v) = w(u, v) + h(v) − h(u).

Знаки здесь не «наоборот», а те же: перевес всегда выглядит как w + p(u) − p(v), где p - потенциал. У Джонсона потенциал - это h[v], расстояние ОТ фиктивного старта, и подстановка p = h даёт w + h[u] − h[v]. У A* потенциал - это −h(v), оценка расстояния ДО цели со знаком минус (чем ближе к цели, тем «ниже» вершина), и та же формула w + p(u) − p(v) даёт w − h(u) + h(v). Одно правило, два разных потенциала - отсюда и разный порядок слагаемых.

Посчитаем, во что превращается приоритет. Длина пути от старта до v по новым весам равна g(v) + h(v) − h(старт) - это та же телескопическая сумма, что в прошлой главе. Слагаемое h(старт) одинаково для всех вершин, значит порядок по новой длине совпадает с порядком по g(v) + h(v), то есть по f. Вывод: A* - это буквально Дейкстра на перевешенном графе, и никакой новой теории он не требует.

Отсюда же сразу видно условие, при котором всё работает: Дейкстре нужны неотрицательные веса, значит нужно w(u, v) + h(v) − h(u) >= 0, то есть h(u) <= w(u, v) + h(v). Это и есть условие СОГЛАСОВАННОСТИ эвристики, и ему посвящён следующий урок.

Разбор: сколько вершин экономится

Возьмём открытую сетку без препятствий, 16 × 10, старт в углу (0,0), цель - в конце той же строки, (15,0). Дейкстра раскрывает вершины по возрастанию расстояния от старта, то есть «ромбом» во все стороны: до цели с расстоянием 15 она успевает раскрыть 105 клеток из 160.

A* с манхэттенской эвристикой идёт по строке 0 и раскрывает ровно 15 клеток - весь путь и ничего больше. Почему: для клетки (x, y) здесь f = g + h = (x + y) + (15 − x) + y = 15 + 2y, и любая клетка вне нулевой строки сразу получает f больше минимального. Оба замера сделаны на движке курса, ответ у обоих алгоритмов одинаковый - 15.

Но это не «всегда в семь раз». Поставь цель в противоположный угол той же открытой сетки - и выигрыш исчезнет: там манхэттенская эвристика ТОЧНА, а оптимальных путей между углами столько, что f одинаково у КАЖДОЙ клетки прямоугольника между стартом и целью. Алгоритм не знает, какой из равных вариантов лучше, и при ничьей по f может раскрыть их все - то есть почти всю сетку, как и Дейкстра. Лечится это правилом разрыва ничьих: при равном f вперёд пускают вершину с меньшим h, то есть ту, что ближе к цели, - тогда A* идёт по одному пути и раскрывает порядка его длины. Выигрыш зависит ещё и от рельефа: лабиринт со стенами заставит A* исследовать тупики, а на карте с редкими дорогами расстояние по прямой сильно недооценивает путь, и алгоритм снова начнёт расходиться кругом.

Асимптотика A* с СОГЛАСОВАННОЙ эвристикой (о ней следующий урок) та же, что у Дейкстры: O((V + E)·log V). Выигрыш - практический, и измеряется он числом раскрытых вершин, а не строчкой оценки.

Что ломается без этого

Первое: класть в очередь f, а сравнивать потом g (или наоборот) - приоритет и величина должны быть согласованы, иначе порядок извлечений станет бессмысленным. Второе: пересчитывать h поиском по графу - эвристика обязана быть ДЕШЁВОЙ (формула по координатам, таблица предподсчёта); если её вычисление стоит как сам поиск, смысла нет. Третье: останавливаться, когда цель впервые попала в очередь: ответ готов только когда цель ИЗВЛЕЧЕНА - до этого её оценка может улучшиться. Четвёртое: применять A* там, где цели нет (нужны расстояния до всех вершин) - эвристика туда просто не подставляется, это задача Дейкстры. Пятое: брать эвристику «на глаз», не проверив, не переоценивает ли она остаток, - об этом следующий урок.

Зачем это в работе

A* - стандарт в играх (поиск пути юнитов по сетке или навигационной сетке), в навигации и робототехнике, в решении головоломок (пятнашки, кубик - там эвристики вроде суммы манхэттенских расстояний), в планировании действий. На больших дорожных графах его усиливают предподсчитанными эвристиками: выбирают «ориентиры» (landmarks), считают от них расстояния заранее и получают куда более точную оценку, чем расстояние по прямой, - это семейство называют ALT. И ещё одно практическое: A* естественно сочетается с двунаправленным поиском и с поиском по сетке с крупными блоками. Но главное, что стоит унести: никакой магии здесь нет - это Дейкстра, которой подсказали направление потенциалом.

Теперь сам

Сетка 3 × 3, ходить можно вверх/вниз/влево/вправо, каждый шаг стоит 1. Старт - клетка (0,0), цель - (2,2). Эвристика - манхэттенское расстояние. Посчитай f для клеток (0,1), (1,1) и (1,0) после первого шага и скажи, какую из них A* раскроет первой.

Ответ. Манхэттенская эвристика: h(x, y) = |2 − x| + |2 − y|. Клетка (0,1): путь от старта g = 1, остаток h = |2−0| + |2−1| = 3, значит f = 4. Клетка (1,0): g = 1, h = |2−1| + |2−0| = 3, f = 4. Клетка (1,1): с первого шага она недостижима (по диагонали ходить нельзя), поэтому в очередь после первого шага она не попадает вовсе; если добраться до неё за два шага, будет g = 2, h = |2−1| + |2−1| = 2, f = 4.

Первой A* раскроет любую из двух клеток с f = 4 - они равноценны, и выбор зависит от того, как куча разрешает равенство. И обрати внимание на главное: у ВСЕХ клеток этой сетки f = 4, потому что любой путь из угла в угол по четырём направлениям оптимален - манхэттенская эвристика здесь точна, и «в стороне» на открытой сетке 3 × 3 просто нет клеток. Именно этот случай и разобран выше: когда все варианты равны, эвристика не сокращает поиск, пока не задано правило разрыва ничьих.

Чтобы увидеть, как она откладывает невыгодный ход, сдвинь старт в центр: из клетки (1,1) (f = 0 + 2 = 2) шаг в (0,1) даёт g = 1, h = 3, f = 4, а шаг в (2,1) - g = 1, h = 1, f = 2. Ход «от цели» вдвое дороже по оценке и будет ждать в очереди, пока не кончатся ходы получше. Так эвристика и работает: она не запрещает ходы, она откладывает невыгодные.

Главное

A* - это Дейкстра, которой сообщили, где цель: из очереди берётся вершина с наименьшим f(v) = g(v) + h(v), где g - реально пройденное расстояние, а h - дешёвая ОЦЕНКА остатка пути до цели. При h ≡ 0 алгоритм вырождается в Дейкстру; чем точнее эвристика, тем меньше вершин раскрывается: на замере в открытой сетке 16 × 10 это 15 раскрытий против 105 у Дейкстры при том же ответе. Когда оптимальных путей много и f у них одинаково, нужно правило разрыва ничьих (при равном f вперёд идёт меньшее h), иначе выигрыш пропадает. Формально A* эквивалентен Дейкстре на графе с перевешенными рёбрами w'(u, v) = w(u, v) + h(v) − h(u): это те же потенциалы, что у Джонсона, только берутся они не из Беллмана-Форда, а из оценки расстояния до цели; длина любого пути меняется на h(v) − h(старт), поэтому порядок путей сохраняется. Отсюда и требование к эвристике: новые веса должны быть неотрицательны, то есть h(u) <= w(u, v) + h(v). Ответ считается готовым, когда цель ИЗВЛЕЧЕНА из очереди, а не когда впервые в неё попала. Асимптотика в худшем случае та же, что у Дейкстры, - выигрыш практический, в числе раскрытых вершин.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.