BFS: חיפוש לרוחב (Breadth-First Search)
עודכן לאחרונה
חיפוש לרוחב סורק גרף רמה אחר רמה. הוא מתחיל מצומת מקור, מבקר קודם בכל השכנים הישירים שלו, אחר כך בכל השכנים שלהם שעוד לא ביקר בהם, וכן הלאה, ומתפשט החוצה בטבעות של מרחק הולך וגדל. לחצו על הפעלה למעלה כדי לראות אותו מתפרס מצומת ההתחלה שכבה אחת בכל פעם.
BFS משתמש בתור מסוג נכנס ראשון, יוצא ראשון, וזה מה שאוכף את הסדר של רמה אחר רמה. מכיוון שהוא מגיע לצמתים לפי סדר מספר הקפיצות, BFS מוצא את המסלול הקצר ביותר (הכי מעט קשתות) בגרף לא ממושקל. הוא מבקר בכל צומת ובכל קשת פעם אחת, ולכן רץ בזמן O(V + E).
סיבוכיות זמן וזיכרון
| מדד | סיבוכיות | הערות |
|---|---|---|
| זמן | O(V + E) | ביקור אחד בכל צומת וקשת |
| זיכרון | O(V) | תור וקבוצת ביקורים, במקרה הגרוע כל הצמתים |
| סדר מעבר | רמה אחר רמה | הצמתים הקרובים קודם, בטבעות |
| מסלול קצר ביותר | כן (לא ממושקל) | מגיע לכל צומת בהכי מעט קשתות |
צעד אחר צעד
| צעד | מה קורה |
|---|---|
| 1 | מכניסים את צומת המקור לתור. |
| 2 | מוציאים את הצומת שבראש התור ומסמנים אותו כמבוקר. |
| 3 | בודקים כל אחד מהשכנים שלו. |
| 4 | מכניסים לתור כל שכן שעוד לא ביקרו בו ושאינו כבר בתור. |
| 5 | חוזרים על כך עד שהתור מתרוקן. |
דוגמה מפורטת
מעבר על הגרף הזה מצומת 0, כאשר 0-1, 0-2, 1-3, 2-3, 2-4 הן הקשתות:
| צעד | מבוקרים | תור (חזית) |
|---|---|---|
| התחלה | {} | [0] |
הוצאת 0 | {0} | [1, 2] |
הוצאת 1 | {0, 1} | [2, 3] |
הוצאת 2 | {0, 1, 2} | [3, 4] |
הוצאת 3 | {0, 1, 2, 3} | [4] |
הוצאת 4 | {0, 1, 2, 3, 4} | [] (סיום) |
מתי להשתמש ב-BFS
| השתמשו בו כאשר | הימנעו ממנו כאשר |
|---|---|
| אתם צריכים את המסלול הקצר ביותר בגרף לא ממושקל | לקשתות יש משקלים: השתמשו ב-Dijkstra במקום |
| סביר שהיעד קרוב למקור | הגרף רחב מאוד: התור עלול להחזיק חזית עצומה |
| אתם רוצים לסרוק גרף לפי סדר רמות | צריך רק להגיע לצומת כלשהו, ושם DFS משתמש בפחות זיכרון |
| אתם מחפשים רכיבי קשירות או מסלול עם מינימום קפיצות | אתם צריכים מיון טופולוגי או זיהוי מעגלים: DFS מתאים יותר |
BFS מול DFS
שניהם עוברים על כל הצמתים ב-O(V + E), אבל הסדר ומבנה הנתונים שונים. ראו את ההדמיה של חיפוש לעומק כדי להשוות ביניהם זה לצד זה.
| היבט | BFS | DFS |
|---|---|---|
| מבנה נתונים | תור (FIFO) | מחסנית או רקורסיה |
| סדר | רמה אחר רמה (הקרובים קודם) | עמוק לאורך ענף אחד, ואז חזרה לאחור |
| מסלול קצר ביותר (לא ממושקל) | כן: הכי מעט קשתות | לא: אין הבטחה |
| זיכרון בגרפים רחבים | גבוה: החזית עלולה להיות עצומה | נמוך: מסלול אחד בכל פעם |
| הכי מתאים ל | מינימום קפיצות, רכיבי קשירות | זיהוי מעגלים, מיון טופולוגי, backtracking |
קוד Breadth-First Search
מימוש נקי של Breadth-First Search שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.
קוד Breadth-First Search ב-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 ב-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 ב-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 ב-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 ב-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?
O(V + E), כאשר V הוא מספר הצמתים ו-E מספר הקשתות, כי הוא מבקר בכל צומת פעם אחת ובודק כל קשת פעם אחת. הוא משתמש ב-O(V) זיכרון עבור התור וקבוצת הביקורים.האם BFS מוצא את המסלול הקצר ביותר?
מה ההבדל בין BFS ל-DFS?
מתי כדאי להשתמש ב-BFS במקום באלגוריתם של Dijkstra?
O(V + E) בלי תור עדיפויות. האלגוריתם של Dijkstra נחוץ כשלקשתות יש משקלים שונים; הרצת BFS רגיל על גרף ממושקל נותנת את המסלול עם הכי מעט קפיצות, לא את הזול ביותר.