BFS

אחד האלגוריתמים הפשוטים לחיפוש גרפים, ולביצוע SSSP .
בהינתן גרף G=(V,E) וקודקוד מקור sV , BFS עובר בצורה סיסטמטית על קשתות הגרף ומבקר בכל קודקוד v הנגיש מ s (נגיש = קיים מסלול).
תוך כדי האלגוריתם מחשב את המרחק (מספר הקשתות במסלול) מs לכל קודקוד נגיש, נוכיח בהמשך שזה המסלול הקצר ביותר. בנוסף הוא מייצר עץ רוחב Breadth-First Tree או עץ מסלולים ששורשו s שמכיל את כל הקודקודים והמסלולים הפשוטים הנגישים מ s.

כמה דגשים חשובים:
א) האלגוריתם עובד עם גרף מכוון ולא מכוון. ללא פונקציית משקל.
ב) האלגוריתם מבקש בקודקודים במרחק k מ s לפני אלה שנמצאים במרחק k+1

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

עבור קשת (u,v)E אם u שחור אז בהכרח v או אפור או שחור לפי מה שאמרנו.
כמו כן, נשייך לכל קודקוד ערך π המהווה קודקוד האבא בעץ הרוחב. כל הקודקודים מתחילים עם π=NULL וכשנציב ערך , הוא לא ישתנה לאורך כל האלגוריתם.
בנוסף בדומה לאלגוריתמים נוספים שמחשבים את ה SSSP , לכל קודקוד משוייך ערך d שבסיום הריצה יכיל את המרחק המינימלי של הקודקוד הזה מ s, ואם אין כזה הערך יישאר .

BFS(G,s)
	for each u in V-{s}:
		u.color = WHITE
		u.d = ∞
		u.𝝅 = NIL
	s.color = GRAY
	s.d = 0
	s.𝝅 = NIL
	Q= {}
	Q.enqueue(s)
	while Q != {}:
		u = Q.dequeue()
		for each v in adj(u):
			if v.color == WHITE
				v.color = GRAY
				v.d = u.d + 1
				v.𝝅 = u
				Q.enqueue(v)
		u.color = BLACK

Pasted image 20230115235547.png|400

זמן ריצה

בגלל שלאחר האתחול האלגוריתם לא צובע אף קודקוד בצבע לבן אנחנו מבטיחים שהתנאי if color == white יתקיים עבור כל קודקוד פעם אחת בידיוק.
פעולת הכנסה והוצאה הן בעלות קבועה O(1) וזאת קוראת לכל קודקוד את כל שכניו כלומר O(E) עבור כל הקודקודים. לכן הפעולה הזאת + פעולת האתחול שלוקחת O(V) אומר שהאלגוריתם BFS יעלה לנו:

O(V+E)

מסלולים קצרים ביותר

נרצה להוכיח שבסיום ריצת BFS עבור קודקוד מקור s מתקיים

vV:δ(s,v)=v.d

ואם אין מסלול אז הערך הוא .

נוכיח כעת כמה למות שיעזרו לנו בשביל ההוכחה שBFS מביא את המסלולים הקצרים ביותר עבור גרף לא ממושקל:

אי שיוויון המשולש

יהי G=(V,E) גרף מכוון או לא מכוון ויהי קודקוד מקור s. אזי

(u,v)E:δ(s,v)δ(s,u)+1

הוכחה:

חסם עליון

יהי G=(V,E) גרף מכוון או לא מכוון ויהי קודקוד מקור s. אזי בסיום ריצת BFS

vV:v.dδ(s,v)

הוכחה:
באינדוקצייה על מספר פעולות ההכנסה לתור.
בסיס- הפעולה הראשונה היא הכנסת s. בזמן זה מתקיים 0=δ(s,s)=s.d וכל שאר הקודקודים מקיימים v.d= ולכן הנ״ל מתקיים בשלב זה.

צעד - נניח שהטענה נכונה לאחר הכנסת k קודקודים לתור. נוכיח שהטענה נכונה עבור הקודקוד ה k+1 . נסמן את הקודקוד הזה ב v , שהוכנס תוך כדי סריקת רשימת השכנויות של u .
לפי הנחת האינדוקצייה

u.dδ(s,u)

לפי אי שיוויון המשולש ולפי הנחת האינדוקצייה

v.d=u.d+1δ(s,u)+1δ(s,v)

ואז v נכנס לתור וצבעו אפור, צבעו לא יהפוך ללבן לאורך ריצת האלגוריתם ולכן לא יוכנס שוב לתור כלומר v.d לא ישתנה כדרוש.

לכל היותר שני ערכי d שונים בתור בכל רגע

נניח שבמהלך ריצת BFS על הגרף , התור Q מכיל את הקודקודים [v1,v2,vr] כאשר v1 הוא ראש התור. אזי:

א) vr.dv1.d+1
ב) 1ir:vi.dvi+1.d

נוכיח באינדוקצייה על מספר פעולות התור.
בסיס:
פעולת ההכנסה הראשונה = הכנסת s לתור, הטענה כמובן מתקיימת.
פעולת ההוצאה הראשונה = הוצאת s מהתור, לא נשארו קודקודים, הטענה מתקיימת.

צעד:
נוכיח נכונות אחרי הוצאת או הכנסת קודקוד כאשר מניחים נכונות לכל אלה שנמצאים לפני הפעולה.

הוצאה = נוציא את ראש התור v1 עליו מתקיימת הטענה, לכן אם התור ריק סיימנו. אם לא אז v2 כעת נמצא בראש. לפי הנחת האינדוקצייה

v1.dv2.dv1.d+1v2.d+1

וגם מתקיים

vr.dv1.d+1v2.d+1

זה הוכחה של סעיף א.
נכונות של סעיף ב נובעת מכך שלפי הנחת האינדוקצייה v2.dv3.d גם כ v1 היה בתור והוצאתו לא שינתה ערכים ולכן היחס סדר הזה עדיין נשמר.

הכנסה = נסמן vr+1 כקודקוד שנכנס לסוף התור. כדי להגיע להכנסה הזאת הוצאנו קודקוד u שהוא אבא של vr+1 . מהנחת האינדוקצייה יתקיים עבור הראש החדש של התור ש

u.dv1.d

וזה בלי קשר למתי v1 נכנס לתור, כלומר לא משנה האם הוא היה בתור בזמן הוצאת u או שהוא נכנס לתור כתוצאה מסריקת השכנים.

מהאלגוריתם מתקיים

vr+1.d=u.d+1v1.d+1

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

vr.du.d+1

ולכן

vr.dvr+1.d

כיוון שמהנחת האינדוקצייה אנחנו יודעים שלכל j<r מתקיים vj.dvr+1.d אז סיימנו כעת גם להוכיח את סעיף ב.

מסקנה נניח ש vi נכנס לפני vj לתור במהלך BFS אזי

vi.dvj.d

זה מסתדר עם העובדה שאנחנו סורקים לרוחב את הגרף כלומר מתחילים מהשכנים המיידים של s ומעלים את d עבורם ב 1 וכן הלאה ממשיכים לשכנים שלהם עד שמגיעים לקודקודים ה״רחוקים״ ביותר מ s .

נכונות BFS

נרצה להוכיח כמובן שבסיום ריצת BFS על גרף G=(V,E) עם קודקוד מקור s יתקיים

vV:v.d=δ(s,v)

כמו כן נוכיח שאחד המסלולים הקצרים ביותר מ s ל v הוא המסלול הקצר ביותר מ s ל v.π עם הוספת הקשת (v.π,v) .

נב״שׁ שקיים קודקוד vV כך ש v.dδ(s,v) בסוף האלגוריתם.
בדומה להוכחה ב SSSP אנחנו יודעים להגיד ש sv וש v.d>δ(s,v) , כלומר , קיים מסלול בין s ל v (אחרת הערך היה ומהלמות שהוכחנו למעלה ערך זה לא משתנה בסתירה להנחה שלנו).
נסמן את u הקודקוד הקודם של v במסלול הקצר ביותר שאורכו δ(s,v) ולכן

v.d>δ(s,v)=δ(s,u)+1=u.d+1

כעת נבין מה קורה כשמוציאים את u מהתור. בזמן הזה v יכול להיות צבוע באחד משלושת הצבעים

א) אם הוא לבן: אז נקבל שבבדיקת שכני u נעשה השמה v.d=u.d+1 בסתירה.
ב) אם הוא שחור: זאת אומרת שהוצאנו מהתור לפני שהוצאנו את u ולכן מהמסקנה שהוכחנו, v.du.d בסתירה.
ג) אם הוא אפור: אז קיים קודקוד w כך ש v.π=w ואז v.d=w.d+1 ולפי אותה מסקנה שהראנו w.du.d כלומר v.du.d+1 בסתירה.

סך הכל הוכחנו שלכל קודקוד יתקיים v.d=δ(s,v) בסוף האלגוריתם כדרוש.
כמו כן כאשר האבא של v הוא u אז נקבל בשורת ההשמה של האלגוריתם

v.d=u.d+1

כלומר שהמסלול הקצר ביותר מ s ל v הוא המסלול הקצר ביותר מ s ל u פלוס 1.

עץ הרוחב

BFS בונה עץ רוחב במהלך ריצתו ביחס לערכי π .
נגדיר Predecessor subgraph של G להיות תת הגרף Gπ(Vπ,Eπ) כאשר:

Vπ={vV  |  v.πNull}{s}E.π={(v.π,v)E  |  vVπ{s}}

תת הגרף הוא עץ הרוחב של G עם שורש s וכל הקודקודים הנגישים ממנו. כמו כן לכל הקודקודים ב Vπ מתקיים שתת הגרף הזה מכיל מסלול ייחודי מ s ל v שאורכו δ(s,v) כלומר מסלול קצר ביותר בגרף המקורי.

ההוכחה שאכן עץ הרוחב הוא תת הגרף הזה נובעת ישירות מהאלגוריתם אנחנו יודעים שהאלגוריתם מגדיר

v.π=u(u,v)Eδ(s,v)<

כלומר ההשמה הזאת מתרחשת רק אם היחס בינהם הוא שהם מרכיבים קשת וגם v נגיש מ s . מהשמה זאת ומהוכחת הנכונות של BFS אנחנו יודעים שבסופו של האלגוריתם כל הקודקודים שיסומנו בשחור ירכיבו את המסלולים הקצרים ביותר מ s לכל הקודקודים האחרים ומהגדרת Gπ הוא עץ ולכן מכיל מסלול פשוט ייחודי מ s לכל הקודקודים הנגישים.

הדפסת מסלול קצר ביותר

הפרוצדורה הבאה מדפיסה את הקודקודים הנמצאים על המסלול מ s ל v :

print_path(G,s,v) 
	if v==s
		print s
	else if v.𝜋 == Nil 
		no path from s to v exists
	else print_path(G,s,v.𝜋)
		print v