Разберите алгоритмическую сложность и доказательство корректности алгоритма поиска кратчайшего пути Дейкстры и сравните его с A*; в каких условиях A* значительно лучше, а где — бесполезен
Коротко, с доказательствами и сравнениями. Dijkstra - Постановка: на взвешенном ориентированном/неориентированном графе с весами рёбер w(e)≥0w(e)\ge 0w(e)≥0 ищет кратчайшие расстояния от истока sss до всех вершин. - Сложность (зависит от структуры очереди при извлечении минимума): - простой массив: O(V2)\;O(V^2)O(V2). - двоичная куча (binary heap): O((V+E)logV)=O(ElogV)\;O((V+E)\log V)=O(E\log V)O((V+E)logV)=O(ElogV). - Фибоначчиева куча: O(E+VlogV)\;O(E+V\log V)O(E+VlogV). - Идея корректности (инвариант и индукция): поддерживаем множество уже окончательно найденных вершин SSS. При извлечении вершины uuu с минимальным текущим расстоянием d[u]d[u]d[u] выполняется инвариант: для любой вершины x∈Sx\in Sx∈Sd[x]d[x]d[x] равно длине кратчайшего пути s→xs\to xs→x. Доказательство по индукции: база — sss с d[s]=0d[s]=0d[s]=0. Предположим инвариант верен для всех вершин в SSS. При следующем извлечении uuu предположим противное — существует путь PPP от sss до uuu короче d[u]d[u]d[u]. Рассмотрим первую вершину vvv на PPP, не принадлежащую SSS; её предшественник ppp принадлежит SSS. По индукции d[p]d[p]d[p] — истинная кратчайшая длина до ppp, и при релаксации ребра (p,v)(p,v)(p,v) должно было установиться d[v]≤d[p]+w(p,v)d[v]\le d[p]+w(p,v)d[v]≤d[p]+w(p,v), то есть стоимость до vvv не больше стоимости префикса PPP, что даёт d[v]<d[u]d[v]<d[u]d[v]<d[u]. Тогда куча должна была вытащить vvv раньше uuu, противоречие. Следовательно, извлечённое d[u]d[u]d[u] — оптимально. - Требование: w(e)≥0w(e)\ge 0w(e)≥0 обязательно; при отрицательных рёбрах нужен Беллман–Форд. A* (A-star) - Постановка: целевая (goal-directed) версия поиска пути; вместо глобального минимума по ggg (стоимости от sss) используется приоритет по f(n)=g(n)+h(n)f(n)=g(n)+h(n)f(n)=g(n)+h(n), где h(n)h(n)h(n) — эвристическая оценка оставшейся стоимости до цели. - Условия оптимальности: - admissible (допустимая): ∀n h(n)≤h∗(n)\forall n\; h(n)\le h^*(n)∀nh(n)≤h∗(n), где h∗(n)h^*(n)h∗(n) — истинная стоимость от nnn до цели. Тогда в tree-search A* гарантированно найдёт оптимальный путь. - consistent (монотонная): ∀(n,m) h(n)≤c(n,m)+h(m)\forall (n,m)\; h(n)\le c(n,m)+h(m)∀(n,m)h(n)≤c(n,m)+h(m). Консистентность ⇒ при графовом поиске вершина, попавшая в закрытое множество (closed), не требует повторного открытия и извлекается с байунтовым окончательным значением. - Корректность (эскиз доказательства): если эвристика допустима, то никакая вершина на оптимальном пути до цели не имеет fff-значения больше оптимальной стоимости C∗C^*C∗ — следовательно, A* при извлечении цели с g(goal)=C∗g(goal)=C^*g(goal)=C∗ не мог пропустить более короткий путь. Формально стандартный контрпример/контрпозиция: если A* вернула не оптимальный путь, существует узел на оптимальном пути, имеющий fff-значение меньше fff полученного решения, и он либо был бы извлечён раньше, либо привёл бы к более короткому решению — противоречие. - Сложность: в худшем случае A* экспандит (рассматривает) все вершины с f(n)≤C∗f(n)\le C^*f(n)≤C∗ (и, возможно, некоторые с ===), поэтому в худшем случае, при слабой эвристике, поведение эквивалентно Dijkstra и сложность тех же порядков (например, O(ElogV)\;O(E\log V)O(ElogV) при двоичной куче). В лучшем случае (идеальная эвристика h=h∗h=h^*h=h∗) A* расширяет только вершины на оптимальном пути — число расширенных вершин может быть порядка длины пути. Сравнение и когда A* полезен / бесполезен - Когда A* значительно лучше: - имеется информативная допустимая эвристика hhh близкая к h∗h^*h∗ (малый остаток), тогда пространство поиска резко сокращается; типичный пример — маршрут на плоскости с эвристикой евклидова/манхэттенова дистанция для положительных геометрических весов. - граф большой, но цель известна и локализована — A* направляет поиск к цели и не «различивает» весь граф. - эвристика консистентна ⇒ меньший overhead (нет повторных открытий). - Когда A* бесполезен или невыгоден: - слабая эвристика, особенно h≡0h\equiv 0h≡0 — A* сводится к Dijkstra, ускорения нет. - если нужно кратчайшее расстояние до всех вершин (вместо одного конкретного целевого узла) — Dijkstra естественнее, т.к. A* ориентирован на одиночную цель. - множество различных целей подряд — лучше предобработки (например, многоисточниковые алгоритмы, алгоритмы с преландмарками) чем многократный A* без инвалидаций. - если вычисление эвристики дорогое — накладные расходы могут перекрыть выигрыш от сокращения числа расширений. - если веса могут быть отрицательными — ни Dijkstra, ни стандартный A* не применимы (нужен Bellman–Ford / алгоритмы для графов с отрицателями). - если эвристика недопустима (overestimate), A* может вернуть не оптимальный путь. Практические замечания - Консистентность удобна вплоть до того, что тогда закрытое множество не нуждается в повторном рассмотрении; при недомонотонном hhh нужен reopening (и корректность сложнее поддерживать). - Для многократных запросов часто выгодны предобученные/прелендмарки (ALT), которые делают A* «почти мгновенным». - Выбор структуры данных (куча, хеши) влияет на константы и поведение при плотных/разреженных графах. Кратко: Dijkstra — гарантированно оптимален и эффективен для поиска всех кратчайших при неотрицательных весах; A* — Dijkstra + эвристика, сохраняет оптимальность при допустимой hhh и может резко сократить поисковое пространство, но только если эвристика информативна и недорогая; при слабой или отсутствующей эвристике A* по сути бесполезен и сводится к Dijkstra.
Dijkstra
- Постановка: на взвешенном ориентированном/неориентированном графе с весами рёбер w(e)≥0w(e)\ge 0w(e)≥0 ищет кратчайшие расстояния от истока sss до всех вершин.
- Сложность (зависит от структуры очереди при извлечении минимума):
- простой массив: O(V2)\;O(V^2)O(V2).
- двоичная куча (binary heap): O((V+E)logV)=O(ElogV)\;O((V+E)\log V)=O(E\log V)O((V+E)logV)=O(ElogV).
- Фибоначчиева куча: O(E+VlogV)\;O(E+V\log V)O(E+VlogV).
- Идея корректности (инвариант и индукция): поддерживаем множество уже окончательно найденных вершин SSS. При извлечении вершины uuu с минимальным текущим расстоянием d[u]d[u]d[u] выполняется инвариант: для любой вершины x∈Sx\in Sx∈S d[x]d[x]d[x] равно длине кратчайшего пути s→xs\to xs→x. Доказательство по индукции: база — sss с d[s]=0d[s]=0d[s]=0. Предположим инвариант верен для всех вершин в SSS. При следующем извлечении uuu предположим противное — существует путь PPP от sss до uuu короче d[u]d[u]d[u]. Рассмотрим первую вершину vvv на PPP, не принадлежащую SSS; её предшественник ppp принадлежит SSS. По индукции d[p]d[p]d[p] — истинная кратчайшая длина до ppp, и при релаксации ребра (p,v)(p,v)(p,v) должно было установиться d[v]≤d[p]+w(p,v)d[v]\le d[p]+w(p,v)d[v]≤d[p]+w(p,v), то есть стоимость до vvv не больше стоимости префикса PPP, что даёт d[v]<d[u]d[v]<d[u]d[v]<d[u]. Тогда куча должна была вытащить vvv раньше uuu, противоречие. Следовательно, извлечённое d[u]d[u]d[u] — оптимально.
- Требование: w(e)≥0w(e)\ge 0w(e)≥0 обязательно; при отрицательных рёбрах нужен Беллман–Форд.
A* (A-star)
- Постановка: целевая (goal-directed) версия поиска пути; вместо глобального минимума по ggg (стоимости от sss) используется приоритет по f(n)=g(n)+h(n)f(n)=g(n)+h(n)f(n)=g(n)+h(n), где h(n)h(n)h(n) — эвристическая оценка оставшейся стоимости до цели.
- Условия оптимальности:
- admissible (допустимая): ∀n h(n)≤h∗(n)\forall n\; h(n)\le h^*(n)∀nh(n)≤h∗(n), где h∗(n)h^*(n)h∗(n) — истинная стоимость от nnn до цели. Тогда в tree-search A* гарантированно найдёт оптимальный путь.
- consistent (монотонная): ∀(n,m) h(n)≤c(n,m)+h(m)\forall (n,m)\; h(n)\le c(n,m)+h(m)∀(n,m)h(n)≤c(n,m)+h(m). Консистентность ⇒ при графовом поиске вершина, попавшая в закрытое множество (closed), не требует повторного открытия и извлекается с байунтовым окончательным значением.
- Корректность (эскиз доказательства): если эвристика допустима, то никакая вершина на оптимальном пути до цели не имеет fff-значения больше оптимальной стоимости C∗C^*C∗ — следовательно, A* при извлечении цели с g(goal)=C∗g(goal)=C^*g(goal)=C∗ не мог пропустить более короткий путь. Формально стандартный контрпример/контрпозиция: если A* вернула не оптимальный путь, существует узел на оптимальном пути, имеющий fff-значение меньше fff полученного решения, и он либо был бы извлечён раньше, либо привёл бы к более короткому решению — противоречие.
- Сложность: в худшем случае A* экспандит (рассматривает) все вершины с f(n)≤C∗f(n)\le C^*f(n)≤C∗ (и, возможно, некоторые с ===), поэтому в худшем случае, при слабой эвристике, поведение эквивалентно Dijkstra и сложность тех же порядков (например, O(ElogV)\;O(E\log V)O(ElogV) при двоичной куче). В лучшем случае (идеальная эвристика h=h∗h=h^*h=h∗) A* расширяет только вершины на оптимальном пути — число расширенных вершин может быть порядка длины пути.
Сравнение и когда A* полезен / бесполезен
- Когда A* значительно лучше:
- имеется информативная допустимая эвристика hhh близкая к h∗h^*h∗ (малый остаток), тогда пространство поиска резко сокращается; типичный пример — маршрут на плоскости с эвристикой евклидова/манхэттенова дистанция для положительных геометрических весов.
- граф большой, но цель известна и локализована — A* направляет поиск к цели и не «различивает» весь граф.
- эвристика консистентна ⇒ меньший overhead (нет повторных открытий).
- Когда A* бесполезен или невыгоден:
- слабая эвристика, особенно h≡0h\equiv 0h≡0 — A* сводится к Dijkstra, ускорения нет.
- если нужно кратчайшее расстояние до всех вершин (вместо одного конкретного целевого узла) — Dijkstra естественнее, т.к. A* ориентирован на одиночную цель.
- множество различных целей подряд — лучше предобработки (например, многоисточниковые алгоритмы, алгоритмы с преландмарками) чем многократный A* без инвалидаций.
- если вычисление эвристики дорогое — накладные расходы могут перекрыть выигрыш от сокращения числа расширений.
- если веса могут быть отрицательными — ни Dijkstra, ни стандартный A* не применимы (нужен Bellman–Ford / алгоритмы для графов с отрицателями).
- если эвристика недопустима (overestimate), A* может вернуть не оптимальный путь.
Практические замечания
- Консистентность удобна вплоть до того, что тогда закрытое множество не нуждается в повторном рассмотрении; при недомонотонном hhh нужен reopening (и корректность сложнее поддерживать).
- Для многократных запросов часто выгодны предобученные/прелендмарки (ALT), которые делают A* «почти мгновенным».
- Выбор структуры данных (куча, хеши) влияет на константы и поведение при плотных/разреженных графах.
Кратко: Dijkstra — гарантированно оптимален и эффективен для поиска всех кратчайших при неотрицательных весах; A* — Dijkstra + эвристика, сохраняет оптимальность при допустимой hhh и может резко сократить поисковое пространство, но только если эвристика информативна и недорогая; при слабой или отсутствующей эвристике A* по сути бесполезен и сводится к Dijkstra.