E024. Breadth-first search

e-maxx algorithm original: C/C++ #algorithm #bfs #emaxx #graph #queue #search
Văn bản bài toán được dịch từ tiếng Nga theo ngôn ngữ giao diện. Mã không thay đổi.

Источник: 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ửi
using 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ốc
vector < 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ửi
import 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ị.

Tất cả việc làm
Chưa có việc làm đang hoạt động.