Menu
Coddy logo textTech

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), אבל הסדר ומבנה הנתונים שונים. ראו את ההדמיה של חיפוש לעומק כדי להשוות ביניהם זה לצד זה.

היבטBFSDFS
מבנה נתוניםתור (FIFO)מחסנית או רקורסיה
סדררמה אחר רמה (הקרובים קודם)עמוק לאורך ענף אחד, ואז חזרה לאחור
מסלול קצר ביותר (לא ממושקל)כן: הכי מעט קשתותלא: אין הבטחה
זיכרון בגרפים רחביםגבוה: החזית עלולה להיות עצומהנמוך: מסלול אחד בכל פעם
הכי מתאים למינימום קפיצות, רכיבי קשירותזיהוי מעגלים, מיון טופולוגי, backtracking

מימוש נקי של Breadth-First Search שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.

קוד Breadth-First Search ב-Python

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")))
להריץ את הקוד הזה בעורך ה-Python אונליין

שאלות נפוצות על חיפוש לרוחב

מהי סיבוכיות הזמן של BFS?
BFS רץ בזמן O(V + E), כאשר V הוא מספר הצמתים ו-E מספר הקשתות, כי הוא מבקר בכל צומת פעם אחת ובודק כל קשת פעם אחת. הוא משתמש ב-O(V) זיכרון עבור התור וקבוצת הביקורים.
האם BFS מוצא את המסלול הקצר ביותר?
כן, בגרף לא ממושקל. מכיוון ש-BFS מגיע לצמתים לפי סדר מספר הקפיצות מהמקור, הפעם הראשונה שהוא מגיע לצומת היא לאורך מסלול עם הכי מעט קשתות. בגרפים ממושקלים צריך במקום זאת את האלגוריתם של Dijkstra.
מה ההבדל בין BFS ל-DFS?
BFS משתמש בתור וסורק רמה אחר רמה (הקרובים קודם), ואילו DFS משתמש במחסנית וצולל עמוק לאורך ענף אחד לפני שהוא חוזר לאחור. BFS מוצא מסלולים קצרים ביותר בגרף לא ממושקל; DFS משתמש בפחות זיכרון בגרפים רחבים ומתאים לזיהוי מעגלים ולמיון טופולוגי.
מתי כדאי להשתמש ב-BFS במקום באלגוריתם של Dijkstra?
השתמשו ב-BFS כשלכל הקשתות יש אותה עלות, כי הוא מוצא את המסלול עם הכי מעט קשתות בזמן O(V + E) בלי תור עדיפויות. האלגוריתם של Dijkstra נחוץ כשלקשתות יש משקלים שונים; הרצת BFS רגיל על גרף ממושקל נותנת את המסלול עם הכי מעט קפיצות, לא את הזול ביותר.
למה BFS צריך קבוצת ביקורים?
גרפים יכולים להכיל מעגלים, ולכן בלי קבוצת ביקורים BFS היה מכניס את אותו צומת לתור שוב ושוב ונתקע בלולאה אינסופית. סימון צומת בפעם הראשונה שמכניסים אותו לתור (ולא כשמוציאים אותו) גם מונע משכנים שונים להוסיף את אותו צומת לתור פעמיים.
האם לסמן צומת כמבוקר כשמכניסים אותו לתור או כשמוציאים אותו?
סמנו אותו כשמכניסים אותו לתור. אם מחכים עד שמוציאים אותו, שכנים שונים יכולים להוסיף אותו לתור כמה פעמים לפני שהוא מעובד, וזה מבזבז זיכרון וזמן. סימון בזמן ההכנסה מבטיח שכל צומת נכנס לתור בדיוק פעם אחת.
איור של שפות התכנות ב-Coddy

לשלוט באלגוריתמים עם Coddy

להתחיל