Menu
Coddy logo textTech

DFS: חיפוש לעומק (Depth-First Search)

עודכן לאחרונה

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

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

סיבוכיות זמן וזיכרון

מדדסיבוכיותהערות
זמןO(V + E)ביקור אחד בכל צומת וקשת
זיכרוןO(V)מחסנית וקבוצת ביקורים, במקרה הגרוע כל הצמתים
סדר מעברהעמוק ביותר קודםהולך בענף אחד עד הסוף לפני האחרים
מבנה נתוניםמחסנית (או רקורסיה)סדר LIFO הוא מה שיוצר את ההתנהגות לעומק

צעד אחר צעד

צעדמה קורה
1דוחפים את צומת המקור למחסנית.
2שולפים צומת; אם כבר ביקרו בו, מדלגים עליו.
3מסמנים אותו כמבוקר ורושמים את הקשת שהובילה אליו.
4דוחפים למחסנית את כל השכנים שלו שעוד לא ביקרו בהם.
5חוזרים על כך עד שהמחסנית מתרוקנת.

דוגמה מפורטת

DFS איטרטיבי מ-A על הגרף A → [B, C], B → [D], C → [E], עם דחיפת השכנים לפי הסדר הרשום (מחסנית LIFO שולפת קודם את מה שנדחף אחרון):

צעדמחסנית (הראש מימין)מבוקריםפעולה
1[A]{}דוחפים את המקור A.
2[B, C]{A}שולפים את A, מסמנים כמבוקר, דוחפים את השכנים B ואז C.
3[B, E]{A, C}שולפים את C, מסמנים כמבוקר, דוחפים את השכן E.
4[B]{A, C, E}שולפים את E, מסמנים כמבוקר, אין שכנים.
5[D]{A, C, E, B}שולפים את B, מסמנים כמבוקר, דוחפים את השכן D.
6[]{A, C, E, B, D}שולפים את D, מסמנים כמבוקר, המחסנית ריקה: סיום.

מתי להשתמש ב-DFS

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

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

קוד Depth-First Search ב-Python

Python
1def dfs(graph, node, visited):2    visited.add(node)3    order = [node]4    for neighbor in graph[node]:5        if neighbor not in visited:6            order.extend(dfs(graph, neighbor, visited))7    return order8
9
10graph = {11    "A": ["B", "C"],12    "B": ["D", "E"],13    "C": ["F"],14    "D": [],15    "E": ["F"],16    "F": [],17}18
19order = dfs(graph, "A", set())20print("DFS order:", " -> ".join(order))
להריץ את הקוד הזה בעורך ה-Python אונליין

שאלות נפוצות על חיפוש לעומק

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

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

להתחיל