מבני נתונים ואלגוריתמים - מחברת קורס/גרפים/חיפוש רוחבי

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי


דף זו עוסק באלגוריתם למציאת המסלול הקצר ביותר בגרף מכוון מצומת מוצא כלשהו.


כדאי לדעת:

בספר הקורס, הפרק "Elementary Graph Algorithms" (תת פרק 2) מכסה נושא זה, אולם אנו נשתמש בגרסה מעט פשוטה יותר מזו המופיעה בספר.



מימוש C++

boost::breadth_first_search


הבעיה[עריכה]

נתונים גרף מכוון וצומת מוצא כלשהו. בהינתן צומת כלשהו, רוצים לדעת מהו המסלול הקצר ביותר מ ל.




דוגמה:

בגרף הבא, נניח צומת מוצא .


בעיית החיפוש הרוחבי.
בעיית החיפוש הרוחבי.


רוצים למצא את המסלול הקצר ביותר מ לכל צומת אחר בגרף. המסלול הקצר ביותר ל, לדוגמה, הוא , וארכו 2.


חיפוש רחבי[עריכה]

הרעיון הכללי[עריכה]

במהלך מציאת הפתרון נחזיק שני מבני נתונים:

  1. Min-Dists, מערך שיחזיק את המרחקים הקצרים ביותר.
  2. q, תור שיחזיק את קבוצת הצמתים שאת מרחקיהם אנו קובעים כעת.

נפעל עפ"י הצעדים הבאים:

  1. בתחילה נאתחל את כל איברי Min-Dists ל, למעט Min-Dists[s], שאותו נאתחל ל0; נכניס את s לq.
  2. כל עוד q אינו ריק, נשלוף ממנו צומת, נעדכן את ערכי Min-Dists של שכניו, ונכניס אותם לתור עפ"י הצורך.



דוגמה:

עבור הגרף שראינו לעיל, וצומת המוצא , הנה המצב ההתחלתי:

מצב התחלתי בחיפוש רוחבי.
מצב התחלתי בחיפוש רוחבי.


כעת 1 בראש התור. נשלוף אותו ונעדכן את שכניו.

צעד 1 בחיפוש רוחבי.
צעד 1 בחיפוש רוחבי.


כעת 2 בראש התור. נשלוף אותו ונעדכן את שכניו.

צעד 2 בחיפוש רוחבי.
צעד 2 בחיפוש רוחבי.


כעת 6 בראש התור. נשלוף אותו ונעדכן את שכניו.

צעד 3 בחיפוש רוחבי.
צעד 3 בחיפוש רוחבי.


כעת 3 בראש התור. נשלוף אותו ונעדכן את שכניו.

צעד 4 בחיפוש רוחבי.
צעד 4 בחיפוש רוחבי.


כעת 7 בראש התור. נשלוף אותו ונעדכן את שכניו.


צעד 5 בחיפוש רוחבי.
צעד 5 בחיפוש רוחבי.


כעת 7 בראש התור, אבל אין את מי לעדכן - שכנו של 7 הוא 5, ול5 יש כבר מרחק ידוע של 3. לכן לא מכניסים את 5 לתור.

ממשיכים כך הלאה עד שהתור מתרוקן.


פסוודו-קוד[עריכה]

להלן הפסוודו-קוד לחיפוש רחבי:


BFS(G, s)
1	q = Make-Queue()

2	Min-Dists = Make-Array( Length(V(G)) )

3	for u in V(G)
4		Min-Dists[u] = u == s? 0 : 
	
5	Enqueue(q, s)
	
6	while Size(q) > 0
7		u = Dequeue(q)
	
8		for v in A(G, u)
9			if Min-Dists[v] > Min-Dists[u] + 1
10				Min-Dists[v] = Min-Dists[u] + 1
11				Enqueue(q, v)
                	
12	return Min-Dists


ולהלן דוגמה לשימוש בו:

1	Min-Dists = BFS(G, 1)

	# Prints 0.
2	Print( Min-Dists[1] )

	# Prints 2.
3	Print( Min-Dists[3] )
	
	# Prints 3.
4	Print( Min-Dists[4] )


הנה הסבר לBFS:

  • ב1 מייצרים את את התור q, וב2 מייצרים את את המערך Min-Dists.
  • הלולאה 6-11 פועלת כל עוד q אינו ריק.
  • 7 מוציאה את האיבר הקדום ביותר שהוכנס ל בq, ו8-11 מעדכנת את שכניו.

נכונות[עריכה]

משפט:

ב11, Min-Dists מכיל את הערכים הנכונים.


שלבי התור[עריכה]

ראשית נוכיח את המשפט הבא, המתאר את מצב ההתור q במהלך הלולאה 6-11.



משפט:

התור q מכיל בכל עת סדרת צמתים מהצורה , עבור כלשהם, כך ש:

  1. Min-Dists[u1] = ... = Min-Dists[ui] =
  2. Min-Dists[v1] = ... = Min-Dists[vj] =

עבור כלשהו.


מה בעצם טוען המשפט?בלולאה 6-11, q מכיל תמיד 0 או יותר צמתים שרשום להם מרחק כלשהו, שלאחריהם 0 או יותר צמתים שרשום להם מרחק גדול ב1. לעולם לא נראה בו זמנית בq צמתים שלהם שלושה ערכים שונים בMin-Dists.




דוגמה:

  1. בתחילת הדוגמה לעיל, הכיל q את הצומת 1, ואכן: Min-Dists[1] = .
  2. בדוגמה לעיל ראינו מצב שבו בq ישבו הצמתים 3, 7, 5, ואכן:
    1. Min-Dists[3] =
    2. Min-Dists[7] = Min-Dists[5] =



הגדרה:

נאמר שq מגיע לשלב בפעם הראשונה שבה בשורה 6, q מכיל צמתים שלכולם בדיוק אותו ערך בMin-Dists




דוגמה:

בפעם הראשונה שמגיעים לשורה 6, הכיל q את הצומת 1, והיות שMin-Dists[1] = , אז q הגיע לשלב 0.


הוכחת הנכונות בעזרת שלבי התור[עריכה]

בעזרת אפיון תוכן התור בשלבים, נוכל להוכיח את המשפט הבא.



משפט:

כאשר q מגיע לשלב , אז q מכיל בדיוק את הצמתים שמרחקם הקצר ביותר מs הוא .


ראשית נשים לב שהמשפט למעשה אומר שני דברים:

  1. כאשר q מגיע לשלב , אז מרחקו הקצר ביותר של כל צומת בq הוא .
  2. אם מרחקו הקצר ביותר של צומת בq הוא , אז הצומת יהיה בq כאשר q מגיע לשלב .

להלן הוכחת המשפט.


הוכחה: ההוכחה היא באינדוקציה על .

(בסיס האינדוקציה) q נמצא בשלב 0 כאשר מוכנס אליו. קל לראות שהטענה מתקיימת: אכן מרחקו הקצר ביותר מ ל הוא 0, ואין צומת אחר שמרחקו מ הוא 0.

(מעבר האינדוקציה) נניח שהטענה נכונה עד לשלב ה (כולל), ונראה שהיא נכונה לשלב ה.

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

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

ניתוח סיבוכיות[עריכה]

נניח שהגרף נתון ברשימת שכנויות.

1-2 פועלות בבירור בזמן .

3-5 מבצעות פעולות Enqueue, ולכן אורכות זמן במקרה הגרוע.

כפי שראינו, כל צומת נכנס לכל היותר פעם אחת לq, ולכן 6-11 רצה לכל היותר פעמים. 7 היא , ולכן תורמת סה"כ‏ .‏ 8-11 רצות פעמים לכל צומת u, ולכן רצות סה"כ ‏ פעמים לכל היותר. 9-11 הן , ולכן תורמות סה"כ ‏.

הסיבוכיות, לכן, היא במקרה הגרוע.

הפרק הקודם:
אלגוריתם Prim
חיפוש רוחבי
תרגילים
הפרק הבא:
אלגוריתם Dijkstra