E024. Breadth-first search
Источник: e-maxx.ru/algo, страница PDF 82.
Breadth-first search (обход в ширину, breadth-first search) — это один из основных Thuật toánов на đồ thịах. В результате поиска в ширину находится путь кратчайшей длины в невзвешененном đồ thịе, т.е. путь, содержащий наименьшее number рёбер.
Thuật toán работает за
, где
— number вершин,
— number рёбер.
Описание Thuật toánа
На Đầu vào Thuật toánа подаётся заданный đồ thị (невзвешенный), и номер стартовой вершины
. đồ thị может быть
как ориентированным, так и неориентированным, для Thuật toánа это не важно. Сам Thuật toán можно понимать как процесс "поджигания" đồ thịа: на нулевом шаге поджигаем только вершину
. На
каждом следующем шаге огонь с каждой уже горящей вершины перекидывается на всех её соседей; т.е. за одну итерацию Thuật toánа происходит расширение "кольца огня" в ширину на единицу (отсюда и название Thuật toánа). Более строго это можно представить следующим образом. Создадим очередь
, в которую будут помещаться
горящие вершины, а также заведём булевский mảng
, в котором для каждой вершины будем отмечать, горит
она уже или нет (или иными словами, была ли она посещена).
Изначально в очередь помещается только vertex
, и
, а для всех остальных
вершин
. Затем Thuật toán представляет собой цикл: пока очередь не пуста, достать из её головы одну вершину, просмотреть все рёбра, исходящие из этой вершины, и если какие-то из просмотренных вершин ещё не горят, то поджечь их и поместить в конец очереди. В итоге, когда очередь опустеет, обход в ширину обойдёт все достижимые из
вершины, причём до каждой
дойдёт кратчайшим путём. Также можно посчитать длины кратчайших путей (для чего просто надо завести mảng
длин путей
), и компактно сохранить информацию, достаточную для восстановления всех этих кратчайших путей
(для этого надо завести mảng "предков"
, в котором для каждой вершины хранить номер вершины, по которой мы попали в эту вершину).
Cài đặt
Реализуем вышеописанный Thuật toán на языке C++. Đầu vàoные данные:
vector < vector<int> > g; // đồ thị
int n; // number вершин
int s; // стартовая vertex (вершины везде нумеруются с нуля)
// чтение đồ thịа
... Сам обход:
queue<int> q;
q.push (s);
vector<bool> used (n);
vector<int> d (n), p (n);
used[s] = true;
p[s] = -1;
while (!q.empty()) {
int v = q.front();
q.pop();
for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (!used[to]) {
used[to] = true;
q.push (to);
d[to] = d[v] + 1;
p[to] = v;
} } } Если теперь надо восстановить и вывести đường đi ngắn nhất до какой-то вершины
, это можно сделать
следующим образом:
if (!used[to])
cout << "No path!";
else {
vector<int> path;
for (int v=to; v!=-1; v=p[v])
path.push_back (v);
reverse (path.begin(), path.end());
cout << "Path: ";
for (size_t i=0; i<path.size(); ++i)
cout << path[i] + 1 << " ";
}
Приложения Thuật toánа
● Поиск кратчайшего пути в невзвешенном đồ thịе.
● Connected components в đồ thịе за
. Для этого мы просто запускаем обход в ширину от каждой вершины, за исключением вершин, оставшихся
посещёнными (
) после предыдущих запусков. Таким образом, мы выполняем обычный запуск в ширину
от каждой вершины, но не обнуляем каждый раз mảng
, за счёт чего мы каждый раз будем обходить
новую компоненту связности, а суммарное Thời gian chạy Thuật toánа составит по-прежнему
(такие
несколько запусков обхода на đồ thịе без обнуления mảngа
называются серией обходов в ширину).
● Нахождения решения какой-либо задачи (игры) с наименьшим numberм ходов, если каждое
состояние системы можно представить вершиной đồ thịа, а переходы из одного состояния в другое — рёбрами đồ thịа. Классический Ví dụ — игра, где робот двигается по полю, при этом он может передвигать ящики, находящиеся на этом же поле, и it is required за наименьшее number ходов передвинуть ящики в требуемые позиции. Решается это обходом в ширину по đồ thịу, где состоянием (вершиной) является набор координат: координаты робота, и координаты всех коробок.
● Нахождение кратчайшего пути в 0-1-đồ thịе (т.е. đồ thịе взвешенном, но с весами равными только 0 либо 1):
достаточно немного модифицировать Breadth-first search: если текущее edge нулевого веса, и происходит улучшение расстояния до какой-то вершины, то эту вершину добавляем не в конец, а в начало очереди.
● Нахождение кратчайшего цикла в неориентированном невзвешенном đồ thịе: производим Breadth-first search
из каждой вершины; как только в процессе обхода мы пытаемся пойти из текущей вершины по какому-то ребру в уже посещённую вершину, то это означает, что мы нашли кратчайший цикл, и останавливаем обход в ширину; среди всех таких найденных циклов (по одному от каждого запуска обхода) выбираем кратчайший.
● find все рёбра, лежащие на каком-либо кратчайшем пути между заданной парой вершин
.
Для этого надо запустить 2 поиска в ширину: из
, и из
. Обозначим через
mảng кратчайших
расстояний, полученный в результате первого обхода, а через
— в результате второго обхода. Теперь для
любого ребра
легко проверить, лежит ли он на каком-либо кратчайшем пути: критерием будет
Đề bài
.
● find все вершины, лежащие на каком-либо кратчайшем пути между заданной парой вершин
.
Для этого надо запустить 2 поиска в ширину: из
, и из
. Обозначим через
mảng кратчайших
расстояний, полученный в результате первого обхода, а через
— в результате второго обхода. Теперь для
любой вершины
легко проверить, лежит ли он на каком-либо кратчайшем пути: критерием будет
Đề bài
.
● find кратчайший чётный путь в đồ thịе (т.е. путь чётной длины). Для этого надо построить
вспомогательный đồ thị, vertexми которого будут состояния
, где
— номер текущей вершины,
— текущая чётность. Любое edge
исходного đồ thịа в этом новом đồ thịе превратится в два
ребра
и
. После этого на этом đồ thịе надо обходом в ширину find đường đi ngắn nhất из стартовой вершины в конечную, с чётностью, равной 0.
Задачи в online judges
Список задач, которые можно сдать, используя обход в ширину:
● SGU #213 "Strong Defence" [Complexity: средняя]
C# lời giải
bản nháp tự động, xem lại trước khi gửiusing System;
using System.Collections.Generic;
using System.Linq;
public static class AlgorithmDraft
{
// Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
vector < List<int> > g; // граф
int n; // число вершин
int s; // стартовая вершина (вершины везде нумеруются с нуля)
// чтение графа
...
queue<int> q;
q.push (s);
List<bool> used (n);
List<int> d (n), p (n);
used[s] = true;
p[s] = -1;
while (!q.empty()) {
int v = q.front();
q.pop();
for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (!used[to]) {
used[to] = true;
q.push (to);
d[to] = d[v] + 1;
p[to] = v;
}
}
}
if (!used[to])
Console.WriteLine( "No path!";
else {
List<int> path;
for (int v=to; v!=-1; v=p[v])
path.push_back (v);
reverse (path.begin(), path.end());
Console.WriteLine( "Path: ";
for (size_t i=0; i<path.size(); ++i)
Console.WriteLine( path[i] + 1 << " ";
}
}
C++ lời giải
đã khớp/gốcvector < vector<int> > g; // граф
int n; // число вершин
int s; // стартовая вершина (вершины везде нумеруются с нуля)
// чтение графа
...
queue<int> q;
q.push (s);
vector<bool> used (n);
vector<int> d (n), p (n);
used[s] = true;
p[s] = -1;
while (!q.empty()) {
int v = q.front();
q.pop();
for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (!used[to]) {
used[to] = true;
q.push (to);
d[to] = d[v] + 1;
p[to] = v;
}
}
}
if (!used[to])
cout << "No path!";
else {
vector<int> path;
for (int v=to; v!=-1; v=p[v])
path.push_back (v);
reverse (path.begin(), path.end());
cout << "Path: ";
for (size_t i=0; i<path.size(); ++i)
cout << path[i] + 1 << " ";
}
Java lời giải
bản nháp tự động, xem lại trước khi gửiimport java.util.*;
import java.math.*;
public class AlgorithmDraft {
// Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
vector < ArrayList<Integer> > g; // граф
int n; // число вершин
int s; // стартовая вершина (вершины везде нумеруются с нуля)
// чтение графа
...
queue<int> q;
q.push (s);
ArrayList<Boolean> used (n);
ArrayList<Integer> d (n), p (n);
used[s] = true;
p[s] = -1;
while (!q.empty()) {
int v = q.front();
q.pop();
for (size_t i=0; i<g[v].size(); ++i) {
int to = g[v][i];
if (!used[to]) {
used[to] = true;
q.push (to);
d[to] = d[v] + 1;
p[to] = v;
}
}
}
if (!used[to])
System.out.println( "No path!";
else {
ArrayList<Integer> path;
for (int v=to; v!=-1; v=p[v])
path.push_back (v);
reverse (path.begin(), path.end());
System.out.println( "Path: ";
for (size_t i=0; i<path.size(); ++i)
System.out.println( path[i] + 1 << " ";
}
}
Материал разбит как Thuật toánическая Bài toán: изучить постановку, понять асимптотику и реализовать Thuật toán на выбранном языке.
Vacancies for this task
việc làm đang hoạt động with overlapping task tags are đã hiển thị.