Обход графа в ширину. Кратчайшие пути. Алгоритм Дейкстры

Графы - мощный инструмент для моделирования связей и зависимостей в реальном мире. Обход графа позволяет эффективно находить оптимальные решения многих задач. Давайте разберемся, как устроен обход графа в ширину и как с его помощью находить кратчайшие пути с помощью знаменитого алгоритма Дейкстры.

Основы теории графов

Граф в математике - это множество вершин, соединенных ребрами. Формально граф G задается как пара множеств (V, E), где V - множество вершин, E - множество ребер. Ребро может соединять две вершины или одну вершину саму с собой.

Различают следующие основные типы графов:

  • Ориентированные и неориентированные. В ориентированных графах ребра имеют направление, в неориентированных - нет.
  • Взвешенные и невзвешенные. Во взвешенных графах у ребер есть веса, в невзвешенных все ребра равнозначны.

Для представления графа в памяти компьютера используются разные структуры данных, например:

  • Матрица смежности - двумерный массив, где элемент a[i][j] = 1 если есть ребро из i в j.
  • Список смежности - для каждой вершины список смежных с ней вершин.

Основными характеристиками вершины в графе являются ее степень - число инцидентных ей ребер, и расстояние до других вершин - длина кратчайшего пути.

Обход графа: основные понятия

Обход графа используется для решения таких задач, как:

  • Поиск кратчайшего пути между вершинами
  • Проверка связности графа
  • Поиск компонент связности
  • Топологическая сортировка вершин

Различают два основных способа обхода графа:

  • В глубину (DFS) - сначала идем по одному пути до конца, потом возвращаемся назад и ищем новые пути.
  • В ширину (BFS) - сначала обходим все вершины на расстоянии 1 от стартовой, потом все на расстоянии 2 и т.д.

Обход графа в глубину заключается в следующем:

  1. Выбрать стартовую вершину
  2. Перейти в одну из смежных непосещенных вершин
  3. Повторять шаг 2, пока есть непосещенные вершины
  4. Вернуться назад и повторить с другой вершины

Псевдокод DFS можно представить так:

 DFS(Граф G, вершина v): пометить v как посещенную для каждой вершины w смежной с v: если w не посещена: DFS(G, w) 

Обход графа в ширину происходит следующим образом:

  1. Пометить начальную вершину и добавить ее в очередь
  2. Извлечь первую вершину из очереди
  3. Добавить все смежные непосещенные вершины в очередь
  4. Повторять шаги 2-3, пока очередь не опустеет

Псевдокод BFS:

 BFS(Граф G, вершина s): добавить s в очередь пометить s как посещенную пока очередь не пуста: u = извлечь первый элемент очереди для каждой вершины v смежной с u: если v не посещена: пометить v как посещенную добавить v в очередь 

Сложность обоих алгоритмов составляет O(V+E), где V - число вершин, E - число ребер.

Как видно из описания, обход графа в ширину позволяет найти расстояние от стартовой вершины до всех остальных. Это свойство лежит в основе поиска кратчайших путей с помощью знаменитого алгоритма Дейкстры.

Голограмма с графом

Поиск кратчайших путей в графе

Рассмотрим задачу поиска кратчайшего пути во взвешенном графе. Пусть дан ориентированный граф G = (V, E) с весами ребер w(u, v) для каждого (u, v) из E. Требуется для заданной пары вершин s и t найти путь от s до t с минимальным суммарным весом.

Для решения этой задачи можно использовать алгоритм Дейкстры. Его идея заключается в следующем:

  1. Назначить всем вершинам бесконечный вес, кроме начальной - ей присвоить 0.
  2. Пока есть непросмотренные вершины, выбрать с минимальным весом.
  3. Релаксировать (обновить веса) всех смежных вершин.
  4. Повторять шаги 2-3 до тех пор, пока не переберем все вершины.

Псевдокод алгоритма Дейкстры:

 Дейкстра(Граф G, вершина s): 1. dist[s] ← 0 2. для каждой вершины v отличной от s: 3. dist[v] ← +∞ 4. S ← пустое множество 5. Q ← множество всех вершин графа 6. пока Q не пусто: 7. u ← вершина в Q с наименьшим dist[u] 8. вынуть u из Q 9. добавить u в S 10. для каждой вершины v смежной с u: 11. alt ← dist[u] + w(u, v) 12. если alt < dist[v]: 13. dist[v] ← alt 

Можно доказать, что этот алгоритм действительно находит кратчайшие пути от вершины s до всех остальных. Временная сложность алгоритма Дейкстры составляет O(V^2).

Обход графа в ширину (python)

При реализации алгоритма Дейкстры на Python удобно использовать модуль heapq для хранения очереди с приоритетом на основе кучи. Это позволит извлекать вершину с минимальным весом за O(logV). Тогда временная сложность оптимизированного алгоритма Дейкстры составит O(ElogV).

Город с высоты

Реализация BFS и алгоритма Дейкстры

Рассмотрим пример реализации обхода графа в ширину на языке Python:

 from collections import deque def bfs(graph, start): visited = set() queue = deque([start]) while queue: vertex = queue.popleft() if vertex not in visited: visited.add(vertex) queue.extend(graph[vertex] - visited) return visited 

Здесь мы используем очередь из модуля deque для хранения вершин, ожидающих посещения. Посещенные вершины помещаем во множество visited.

А вот пример реализации алгоритма Дейкстры на Python:

 import heapq def dijkstra(graph, start): distances = {vertex: float('infinity') for vertex in graph} distances[start] = 0 queue = [] heapq.heappush(queue, [distances[start], start]) while queue: current_dist, current_vertex = heapq.heappop(queue) for neighbor, weight in graph[current_vertex].items(): dist = current_dist + weight if dist < distances[neighbor]: distances[neighbor] = dist heapq.heappush(queue, [dist, neighbor]) return distances 

Здесь мы используем кучу heapq для хранения пар [расстояние, вершина] и извлечения вершины с минимальным весом.

Аналогичным образом можно реализовать эти алгоритмы и на других языках программирования, таких как C++, Java, C#.

Оптимизации BFS и алгоритма Дейкстры

Хотя асимптотическая сложность BFS и алгоритма Дейкстры достаточно низка, на практике при работе с большими графами требуется оптимизировать эти алгоритмы для повышения производительности. Рассмотрим основные способы ускорения BFS и Дейкстры.

Оптимизации BFS

  • Двунаправленный BFS - запуск с двух концов одновременно для ускорения встречи посередине.
  • Поиск с итеративным углублением - сначала неглубокий поиск, потом все глубже.
  • Параллельный BFS - распределение поиска по нескольким потокам.

Оптимизации алгоритма Дейкстры

  • Использование кучи для хранения очереди с приоритетом - O(ElogV).
  • Многопоточный Дейкстра для распределенных вычислений.
  • Применение битовых операций для компактного хранения множеств.

Правильный выбор структур данных и распараллеливание могут ускорить BFS и Дейкстру в десятки и сотни раз.

Применение BFS и алгоритма Дейкстры

BFS и алгоритм Дейкстры широко применяются для решения практических оптимизационных задач на графах во многих областях:

  • Поиск кратчайшего пути на карте.
  • Маршрутизация в компьютерных сетях.
  • Планирование движений роботов.
  • Построение деревьев зависимостей в ПО.

Например, Google Maps использует модификацию алгоритма Дейкстры для построения маршрутов. А вот пример поиска кратчайшего пути между двумя точками на карте с помощью NetworkX и OSMnx на Python:

 import networkx as nx import osmnx as ox G = ox.graph_from_point() orig = (35.0, -75.0) dest = (35.1, -80.0) route = nx.shortest_path(G, orig, dest, weight='length') 

Здесь мы загружаем дорожную сеть из OpenStreetMap, преобразуем ее в граф NetworkX, и вызываем алгоритм Дейкстры для поиска кратчайшего пути.

BFS и алгоритм Дейкстры

BFS (breadth first search) — это алгоритм, используемый для обхода или поиска в графах и деревьях. Он начинается с выбранной вершины и обходит сначала все доступные вершины на текущем уровне перед переходом на следующий уровень.

Алгоритм BFS очень похож наВолновой алгоритм, так как волновой алгоритм относится к семейству алгоритмов основанных на методах поиска в ширину.

Обход в ширину работает на основе очереди

Сложность алгоритма обхода в ширину является линейной и равна O(V+E), где V — количество вершин графа, E — количество ребер в графе.

Хотя BFS и алгоритм Дейкстры основаны на схожей идее поиска в ширину, между ними есть важные различия:

  • BFS для невзвешенных графов, Дейкстра - для взвешенных.
  • BFS находит кратчайшие пути по числу ребер, Дейкстра - по весу.
  • Дейкстра эффективнее на больших разреженных графах.

В целом, BFS чаще применяется для проверки связности и поиска компонент, а Дейкстра - для оптимизационных задач.

Альтернативные алгоритмы поиска кратчайших путей

Помимо классического алгоритма Дейкстры, для поиска кратчайших путей часто используются и другие подходы:

  • Алгоритм Беллмана-Форда - для графов с отрицательными ребрами.
  • Алгоритм Флойда-Уоршелла - для всех пар вершин.
  • Алгоритм A* - с эвристикой, например для пути на карте.

Каждый из этих алгоритмов имеет свои плюсы и минусы по сравнению с классическим алгоритмом Дейкстры в зависимости от структуры графа и решаемой задачи.

Статья закончилась. Вопросы остались?
Комментарии 0
Подписаться
Я хочу получать
Правила публикации
Редактирование комментария возможно в течении пяти минут после его создания, либо до момента появления ответа на данный комментарий.