BFS (przeszukiwanie wszerz)
Ostatnia aktualizacja
Przeszukiwanie wszerz przegląda graf poziom po poziomie. Zaczynając od węzła źródłowego, najpierw odwiedza wszystkich jego bezpośrednich sąsiadów, potem wszystkich ich nieodwiedzonych sąsiadów i tak dalej, rozszerzając się na zewnątrz pierścieniami o rosnącej odległości. Kliknij odtwarzanie powyżej i zobacz, jak algorytm rozchodzi się od węzła startowego warstwa po warstwie.
BFS używa kolejki FIFO (pierwszy wszedł, pierwszy wyszedł) i to ona wymusza kolejność poziom po poziomie. Ponieważ BFS dociera do węzłów w kolejności liczby przeskoków, znajduje najkrótszą ścieżkę (najmniej krawędzi) w grafie bez wag. Odwiedza każdy węzeł i każdą krawędź raz, więc działa w czasie O(V + E).
Złożoność czasowa i pamięciowa
| Miara | Złożoność | Uwagi |
|---|---|---|
| Czas | O(V + E) | Każdy wierzchołek i krawędź odwiedzane raz |
| Pamięć | O(V) | Kolejka + zbiór odwiedzonych, w najgorszym razie wszystkie węzły |
| Przechodzenie | Poziom po poziomie | Najpierw najbliższe węzły, pierścieniami |
| Najkrótsza ścieżka | Tak (bez wag) | Dociera do każdego węzła najmniejszą liczbą krawędzi |
Krok po kroku
| Krok | Co się dzieje |
|---|---|
| 1 | Dodaj węzeł źródłowy do kolejki. |
| 2 | Pobierz węzeł z początku kolejki i oznacz go jako odwiedzony. |
| 3 | Przejrzyj każdego z jego sąsiadów. |
| 4 | Dodaj do kolejki każdego sąsiada, który nie był jeszcze odwiedzony ani dodany. |
| 5 | Powtarzaj, aż kolejka będzie pusta. |
Przykład krok po kroku
Przechodzenie tego grafu od węzła 0, gdzie krawędzie to 0-1, 0-2, 1-3, 2-3, 2-4:
| Krok | Odwiedzone | Kolejka (granica) |
|---|---|---|
| Start | {} | [0] |
Pobierz 0 | {0} | [1, 2] |
Pobierz 1 | {0, 1} | [2, 3] |
Pobierz 2 | {0, 1, 2} | [3, 4] |
Pobierz 3 | {0, 1, 2, 3} | [4] |
Pobierz 4 | {0, 1, 2, 3, 4} | [] (koniec) |
Kiedy używać BFS
| Używaj, gdy | Unikaj, gdy |
|---|---|
| Potrzebujesz najkrótszej ścieżki w grafie bez wag | Krawędzie mają wagi: użyj algorytmu Dijkstry |
| Cel prawdopodobnie leży blisko źródła | Graf jest bardzo szeroki: kolejka może pomieścić ogromną granicę |
| Chcesz przeglądać graf w kolejności poziomów | Wystarczy dotrzeć do dowolnego węzła, a DFS zużywa mniej pamięci |
| Szukasz spójnych składowych lub trasy z najmniejszą liczbą przeskoków | Potrzebujesz sortowania topologicznego lub wykrywania cykli: DFS pasuje lepiej |
BFS a DFS
Oba odwiedzają każdy węzeł w O(V + E), ale różnią się kolejnością i strukturą danych. Zobacz wizualizację przeszukiwania w głąb, aby porównać je obok siebie.
| Aspekt | BFS | DFS |
|---|---|---|
| Struktura danych | Kolejka (FIFO) | Stos / rekurencja |
| Kolejność | Poziom po poziomie (najpierw najbliższe) | W głąb jednej gałęzi, potem powrót |
| Najkrótsza ścieżka (bez wag) | Tak: najmniej krawędzi | Nie: brak gwarancji |
| Pamięć w szerokich grafach | Duża: granica może być ogromna | Mała: jedna ścieżka naraz |
| Najlepszy do | Najmniejszej liczby przeskoków, spójnych składowych | Wykrywania cykli, sortowania topologicznego, backtrackingu |
Breadth-First Search: kod
Przejrzysta, gotowa do uruchomienia implementacja algorytmu Breadth-First Search w językach: Python, JavaScript, Java, C++, C. Wybierz język, skopiuj kod albo otwórz go od razu w edytorze online Coddy.
Breadth-First Search: kod (Python)
1from collections import deque2
3
4def bfs(graph, start):5 visited = {start}6 queue = deque([start])7 order = []8 while queue:9 node = queue.popleft()10 order.append(node)11 for neighbor in graph[node]:12 if neighbor not in visited:13 visited.add(neighbor) # mark on enqueue, not dequeue14 queue.append(neighbor)15 return order16
17
18graph = {19 "A": ["B", "C"],20 "B": ["D", "E"],21 "C": ["F"],22 "D": [],23 "E": ["F"],24 "F": [],25}26
27print("BFS order:", " -> ".join(bfs(graph, "A")))Breadth-First Search: kod (JavaScript)
1const graph = {2 A: ["B", "C"],3 B: ["D", "E"],4 C: ["F"],5 D: [],6 E: ["F"],7 F: [],8};9
10function bfs(start) {11 const visited = new Set([start]);12 const queue = [start];13 const order = [];14 while (queue.length > 0) {15 const node = queue.shift(); // Dequeue the oldest node16 order.push(node);17 for (const next of graph[node]) {18 if (!visited.has(next)) {19 visited.add(next);20 queue.push(next);21 }22 }23 }24 return order;25}26
27console.log("BFS from A:", bfs("A").join(" -> "));Breadth-First Search: kod (Java)
1import java.util.ArrayDeque;2import java.util.List;3import java.util.Queue;4
5public class Main {6 public static void main(String[] args) {7 List<List<Integer>> adj = List.of(8 List.of(1, 2), // neighbors of 09 List.of(0, 3, 4), // neighbors of 110 List.of(0, 5),11 List.of(1),12 List.of(1, 5),13 List.of(2, 4)14 );15 boolean[] visited = new boolean[adj.size()];16 Queue<Integer> queue = new ArrayDeque<>();17 visited[0] = true;18 queue.add(0);19
20 StringBuilder order = new StringBuilder("BFS from 0:");21 while (!queue.isEmpty()) {22 int node = queue.poll();23 order.append(" ").append(node);24 // Mark neighbors when enqueued so nothing enters twice25 for (int next : adj.get(node)) {26 if (!visited[next]) {27 visited[next] = true;28 queue.add(next);29 }30 }31 }32 System.out.println(order);33 }34}Breadth-First Search: kod (C++)
1#include <iostream>2#include <queue>3#include <vector>4
5void bfs(int start, const std::vector<std::vector<int>>& adj) {6 std::vector<bool> visited(adj.size(), false);7 std::queue<int> frontier;8 visited[start] = true;9 frontier.push(start);10 // Visit nodes level by level11 while (!frontier.empty()) {12 int node = frontier.front();13 frontier.pop();14 std::cout << node << " ";15 for (int next : adj[node]) {16 if (!visited[next]) {17 visited[next] = true;18 frontier.push(next);19 }20 }21 }22}23
24int main() {25 // 6-node undirected graph as an adjacency list26 std::vector<std::vector<int>> adj = {27 {1, 2}, // 028 {0, 3, 4}, // 129 {0, 5}, // 230 {1}, // 331 {1, 5}, // 432 {2, 4}, // 533 };34 std::cout << "BFS from node 0: ";35 bfs(0, adj);36 std::cout << "\n";37 return 0;38}Breadth-First Search: kod (C)
1#include <stdbool.h>2#include <stdio.h>3
4#define N 65
6void bfs(int start, const int adj[N][N]) {7 bool visited[N] = {false};8 int queue[N]; // fixed-size queue: head chases tail9 int head = 0, tail = 0;10 visited[start] = true;11 queue[tail++] = start;12 // Visit nodes level by level13 while (head < tail) {14 int node = queue[head++];15 printf("%d ", node);16 for (int next = 0; next < N; next++) {17 if (adj[node][next] && !visited[next]) {18 visited[next] = true;19 queue[tail++] = next;20 }21 }22 }23}24
25int main(void) {26 // 6-node undirected graph as an adjacency matrix27 int adj[N][N] = {28 {0, 1, 1, 0, 0, 0},29 {1, 0, 0, 1, 1, 0},30 {1, 0, 0, 0, 0, 1},31 {0, 1, 0, 0, 0, 0},32 {0, 1, 0, 0, 0, 1},33 {0, 0, 1, 0, 1, 0},34 };35 printf("BFS from node 0: ");36 bfs(0, adj);37 printf("\n");38 return 0;39}BFS (przeszukiwanie wszerz): najczęstsze pytania
Jaka jest złożoność czasowa BFS?
O(V + E), gdzie V to liczba wierzchołków, a E liczba krawędzi, bo każdy wierzchołek odwiedza raz i każdą krawędź sprawdza raz. Zużywa O(V) pamięci na kolejkę i zbiór odwiedzonych.Czy BFS znajduje najkrótszą ścieżkę?
Czym różni się BFS od DFS?
Kiedy użyć BFS zamiast algorytmu Dijkstry?
O(V + E) bez kolejki priorytetowej. Algorytm Dijkstry jest potrzebny, gdy krawędzie mają różne wagi; zwykłe BFS na grafie ważonym daje ścieżkę z najmniejszą liczbą przeskoków, a nie najtańszą.Dlaczego BFS potrzebuje zbioru odwiedzonych węzłów?
Grafy mogą zawierać cykle, więc bez zbioru odwiedzonych BFS dodawałby ten sam węzeł do kolejki wielokrotnie i zapętlił się w nieskończoność. Oznaczanie węzła przy pierwszym dodaniu do kolejki (a nie przy pobraniu) zapobiega też dodaniu tego samego węzła dwa razy przez różnych sąsiadów.
Oznaczać węzeł jako odwiedzony przy dodaniu do kolejki czy przy pobraniu?
Oznaczaj go przy dodaniu do kolejki. Jeśli poczekasz do pobrania, węzeł może zostać dodany do kolejki wiele razy przez różnych sąsiadów, zanim zostanie przetworzony, co marnuje pamięć i czas. Oznaczanie przy dodaniu gwarantuje, że każdy węzeł trafi do kolejki dokładnie raz.