Minimum Spanning Tree

מומלץ לקרוא את הגדרות בגרפים לפני שנכנסים לסיכום זה.

יהי G=(V,E) מולטי גרף קשיר, ממושקל, עם פונקציית משקל w:ER . ויהי T=(V,ET) עץ פורש של G .
נגדיר את המשקל של T להיות w(T)=eETw(e).

עץ פורש מינימלי MST של G הוא עץ פורש T=(V,ET) של G עם המשקל המינימלי (ייתכנו כמה כאלה).

Pasted image 20221126121918.png|350

נרצה לתכנן אלגוריתם שמקבל G ופונקציית משקל w, ומוצא עץ פורש מינימלי T .
ישנם שני אלגוריתמים חמדניים שמבצעים זאת ומבוססים על אותו אלגוריתם גנרי ובחירה חמדנית.

הבחנה:

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

חתך

תהי קבוצת קודקודים לא ריקה SV. החלוקה (S,V/S) נקראת חתך של G . והקשתות {e=(u,v) | uS,vV/S} נקראות קשתות החתך (הן אלה שמחברות בין צד אחד לצד שני. מזכיר קצת גרף דו צדדי).

Pasted image 20221126123218.png|350

הבחירה החמדנית

יהי G=(V,E) (מולטי) גרף קשיר, ממושקל, עם פונקצי׳ת משקל w:ER . עבור כל קבוצת קודקודים לא ריקה SV, קיים עץ פורש מינימלי של G , T כך של T מכיל את קשת החתך המינימלית בחתך (S,V/S).

הוכחה
ניקח קבוצת קודקודים S לא ריקה ואת החתך שנוצר איתה. ניקח את קשת החתך המינימלית נסמנה e=(u,v).
יהי T=(V,ET) עץ פורש מינימלי של G ונחלק למקרים:

P=P1eP2

P1 הוא מסלול uu ו P2 הוא מסלול vv .

כעת, נבנה גרף חדש T=(V,ET) שמקיים ET=ET{e}{e} . כלומר החלפנו את הקשת חתך הראשונה במסלול בקשת חתך המינימלית.
נרצה להראות ש T עץ פורש מינימלי. בישביל עץ פורש מינימלי הוא צריך להיות קודם כל

  1. עץ פורש
  2. משקל מינימלי

עץ פורש
מהקשר של T ל T אנחנו יודעים שמתקיים ש

|ET|=|ET|=|V|1

כלומר אם נוכיח ש Tֿֿ קשיר אז יתקיים שהוא עץ ומהגדרה עץ פורש ל G .
נוכיח שקיים מסלול בין כל שתי קודקודים x,y בגרף T. אנחנו יודעים שבהכרח יש מסלול שלהם בעץ T ונסמנו P .
אם הקשת שהורדנו מ T הלוא היא e לא שייכת למסלול P אז אותו המסלול ב T הוא גם המסלול המתאים ב T .
אחרת, eP כלומר הורדנו חלק מהמסלול וצריך למצוא אלטרנטיבה. נניח בלי הגבלתת הכלליות ש u מופיע לפני v במסלול P החדש שנבנה.
הסיבה שהוא בטוח יופיע שם היא שעכשיו הורדנו את e מהגרף האלטרנטיבה היחידה שנוכל לתת היא עם e (נשים לב שגם אין מסלול אחר שהוא לא P כי מדובר בעץ ולא אמורים להיות בו מעגלים).
נסתכל על P ב T

P=P1eP2

כאשר P1 הוא מסלול xu ו P2 הוא מסלול vy . אם כן נבנה את המסלול ל T בצורה הבאה

P1P1ReP2RP2

כלומר מה שעשינו הוא ויתרנו על הקשת e והשתמשנו במסלול ההפוך ל P1,P2 שהגדנו למעלה כך שהראשון מסתיים ב u ואז המסלול ההופכי שלקחנו הראשון יסתיים ב u ואז עם e מגיעים ל v ועושים את אותו תהליך (ההוכחה כך ש v לפני u היא זהה לחלוטין).
אם כן, יש מסלול מכל נקודה לנקודה ולכן הגרף קשיר וסך הכל הוא גם עץ. מהגדרת עץ פורש מתקיים שהוא פורש את הגרף G.

המשקל של T
נשווה בין המשקל של T והמשקל של T .
מתקיים w(e)w(e) כיוון שe קשת קלה יותר (המינימלית).
סך הכל יתקיים

w(T)=w(T{e}{e})=w(T)+w(e)w(e)w(T)

ואמרנו ש T עץ פורש מינימלי משמע w(T)w(T)
סך הכל מחוקי אי שיוויון יתקיים

w(T)=w(T)

ולכן הבחירה החמדנית שאומרת שישנו עץ פורש מינימום שמכיל את הקשת הקלה בחתך נכונה.

צמצום קשתות והאלגוריתם הגנרי לעץ פורש מינימום

הצגנו את תכונת בחירה חמדנית של עצים פורשים מינימליים שנראת בסיס טוב לאלגוריתם חמדן שמוסיף ל MST בכל פעם את קשת החתך המינימלית עבור חתך כלשהו . למרות זאת , ייתכנו כאן שני קשיים :
הראשון , ההגדרה הרקורסיבית שנרצה להשתמש בה , טיפה לא ברורה , אופציה ראשונה היא למחוק קשת החתך המינימלית בחתך כלשהו מהגרף ופותרים באופן רקורסיבי עבור כל אחד משני החתכים , אבל אנחנו לא מבטיחים שאין עוד קשתות חתך שצריכות להיות בעץ הפורש המינימלי .
אופציה שניה למחוק את קשת החתך המינימלית מהגרף , אבל אחרי זה אנו צריכים שיטה להימנע ממעגלים בקשתות שבוחרים ל 𝑇𝑆𝑀.

צמצום קשתות

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

G/e=(V/e,E/e)

והוא הגרף המתקבל מ G ע״י "merging" צירוף הקודקוד u,v לקודקוד אחד בשם uv כלומר

V/e=V{uv}{u,v}

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

f:VV/ef(x)={xxV/{u,v}uvx{u,v}

סך הכל נוכל להגדיר את קבוצת הקשתות של הגרף המצומצם כך

E/e={(f(x),f(y)) | (x,y)E}

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

כעת התכונה הזאת מאפשרת לנו לבנות את האלגוריתם הגנרי הבא

MST-Generic(G=(V,E),w) 
	while |V|>1
		let e be the minimum weight edge of some cut in G
		add e to E_t
		conrtract e
	return E_t	

נשים לב שזה אלגוריתם גנרי ולא מימוש מלא. ישנם שתי אלגוריתמים שהם מימוש של הנ״ל והם נבדלים באיך שהם בוחרים את החתך. בחירה זו משנה את זמן הריצה.

הוכחת תת המבנה האופטימלי לאלגוריתם הגנרי

יהי G=(V,E) מולטי גרף קשיר ממושקל עם פונקציית משקל w .
ויהי (S,V/S) חתך של G ותהי e=(u,v) קשת חתך מינימלית.
תהי T=(V/e,ET) עץ פורש מינימלי של G/e .
אזי, T=(V,ET) כאשר ET=ET{e} הוא עפ״מ של G.

עץ פורש
ראשית נרצה להוכיח |ET|=|V|1 . נשים לב ש T עפ״מ ולכן

|ET|=|V/e|1=|V|2

אבל $$|E_{T}|=|E_{T^{\prime}}|+1= |V|-2+1= |V|-1$$
כעת הוכחנו שבהינתן שהוא גרף קשיר אז הוא עץ.
נוכיח שהוא קשיר, בהינתן שתי קודקודים x,y נמצא את המסלול בינהם.
נפעיל את פונקציית הצמצום f עליהם כיוון ש T הוא עפ״מ שהוא גם צמצום של T . אם לאחר הפעלת f נקבל שתי קודקודים שהמסלול שלהם ב T לא עובר ב uv אז אותו המסלול יעבוד גם ב T.
אחרת הפתרון לבעיה הוא פשוט. כל מסלול שעובר בקודקוד uv יעבור עכשיו בקשת (u,v). לכן T קשיר, הוא גם עץ וגם קשתותיו מוכלות בקשתות של G כלומר הוא עץ פורש של G.

משקל מינימלי
נב״ש ש T אינו עץ פורש מינימלי של G .
ניקח T=(V,E) עפ״מ של G כלומר w(T)<w(T). מתכונת הבחירה החמדנית נוכל להניח ש eE .
כעת ניקח T=(V/e,ET) כאשר ET=ET/{e}. כלומר צמצמו את הקשת המינימלית בחתך של העץ החדש.
נשים לב ש T הוא עפ״מ של G/e כי

  1. |ET|=|ET|1=(|V|1)1=|V/e|1
  2. לכל זוג קודקודים בגרף המצוצמם אם המסלול P בינהם משתמש ב e אז בצמצום זה הפך להיות קודקוד אז המסלול עדיין קביל פשוט במקום לעבור בקשת של הגרף הוא עובר בקודקוד חדש, אחרת , אותו מסלול P קיים גם בגרף המצומצם.
  3. ETE

קיבלנו סך הכל

w(T)=w(T)w(e)<w(T)w(e)=w(T)

קיבלנו

w(T)<w(T)

אבל T הוא עפ״מ באותו גרף מצומצם בסתירה לכך ש T הוא עפ״מ .

סה״כ קיבלנו שבנייה של עפ״מ באופן ריקורסיבי על ידי הוספת קשת חתך מינימלי לתת עץ פורש מינימלי משאירה אותו מינימלי ולכן תכונת תת המבנה האופטימלית מתקיימת.

prim algorithm

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

באופן סכימתי האלגוריתם ייראה ככה:

  1. יצירת קבוצה שמאכלסת את כל הקשתות ב עץ פורש מינימלי. היא מתחילה כקבוצה ריקה.
  2. לכל קודקוד נוסיף מידע p שמייצג את הקודקוד אליו הוא מקושר בחתך S ונוסיף ערך key שמייצג את המשקל אמ״מ הוא קשת חתך עם S אחרת יהיה שם . כמו כן באופן שרירותי ניקח קודקוד אחת וניתן לו מפתח 0
  3. מאתחלים תור קדימויות עם קודקודי V נסמנה Q, יחס הסדר הוא לפי ערכי המפתחות.
  4. כל עוד התור לא ריק נוציא את המינימום u ונבדוק האם הוא מקושר ל S או לא כלומר האם p לא ריק (במילים אחרות נשאלת השאלה האם היא קשת חתך או לא)
  5. אם הוא לא ריק אז מוסיפים אותו לET כי הוא הקשת חתך המינימלית
  6. רצים על כל השכנים של u בלי קשר להאם הוספנו אותו ל ET ובודקים האם שכן v נמצא בתור Q וגם המשקל שלו עם u קטן מ v.key כלומר האם המשקל שלו עם u קטן מהמשקל שלו עם הקודקוד בחתך S .
  7. אם התנאי מתקיים משנים את ערך המפתח של v להיות המשקל שלו עם u ואת p של v

נשים לב לשתי דברים חשובים

  1. הקודקוד הראשון r תמיד יצא באיטרצייה הראשונה.
  2. אנחנו לעולם לא נבחר קודקוד שכבר משוייך ל S אחרת יהיה מעגל וזה בסתירה להגדרת העץ. זה נעשה על ידי הבדיקה האם השכן קיים בערימת מינימום.

ההצטמצמיות מבוצעות ע"י תור 𝑄 כאשר ערך ה- keys לכל קודקוד 𝑢 הוא המשקל של הקשת עם משקל מינימלי שמקשרת בין 𝑢 לקודקוים שנמחקו מ- 𝑄 . באתחול , כל ה- keys להיות ∞ . חוץ מהקודקוד השרירותו r שנבחר לראשונה , המפתח שלו הוא 0 בהתחלה . בנוסף מאתחלים Q להכיל את כל 𝑉 . וכך , הקודקוד הראשון שמוחקים מ- 𝑄 הוא 𝑟 , ואז מעדכנים את המפתחות לכל השכנים של 𝑟 להיות המשקל של הקשת ביניהם לבין 𝑟 . מהאיטרציה השניה והלאה , האינטרפרטציה צריכה להיות שכל הקודקודים שנמחקו מ- Q הצמצמו לקודקוד אחד. וכך , המפתח הקטן ביותר לקודקודים שעדיין ב- 𝑄 מייצגים את הקשת המינימלית שמקשרת אותם לקודקוד היחיד שהוצמצם . ולכן , האלגוריתם מוציא את הקודקוד 𝑢 עם המפתח עם המפתח הקטן ביותר ומוחק 𝑢 מהתור 𝑄. האינטרפרטציה היא שקשת חתך מינימלית מתווספת ל- 𝑇𝑆𝑀. על מנת לבצע צמצום של קשת חדשה שבחרנו להכנה לאיטרציה הקודמת , האלגוריתם סורק את כל השכנים של 𝑢 לבדוק אם המפתח שלו צריך הפחתה , עקב כניסת קשת חדשה לחתך.

MST-PRIM(G=(V,E),w)
	E_T = empty_set
	for each u in V 
		u.key = INFINITY
		u.p = NULL
	set r.key = 0 for some arbitrary r in V

	Q.init(V)
	while |Q| >= 1
		u = Q.extract_min()
		if u.p != NULL
			E_T.add(u,u.p)
		for each v in u.ADJ()
			if Q.contains(v) and weight(u,v)< v.key
				v.key = weight(u,v)
				v.p = u
	return E_T				

Pasted image 20221126223426.png|450

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

operation amount of times Heaps using array Binomial heap or Binary Heaps Fibonacci Heap
Init O(1) O(V) O(V) O(V)
extract_min V O(V) O(log(V)) O(log(V))
decrease_key 2E O(E) O(log(V)) O(1) amortized
total: O(V^2) O(E log(V) O(Vlog(V)+E)

Kruskal algorithm

כדי להבין את האלגוריתם של prim מומלץ לעבור קודם על Union find .

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

  1. מיון הקשתות לפי המשקלים.
  2. סריקתם מהמינימום למקסימום
  3. על כל קשת e נבדוק האם הקשת מייצרת מעגל עם קבוצת הקשתות שכבר הוספנו ל MST
  4. אם היא מייצרת מעגל לא מכניסים אותה, אחרת מכניסים ל MST.

נשים לב שעל מנת לבדוק האם e מייצרת מעגל עלינו להשתמש במבנה נתונים Union Find שמצויין למעלה. כך נוכל לייצג את הרכיבים הקשירים של הקשתות שכבר נבחרו ל MST.
נזכיר שהפעולות שמבנה נתונים זה תומך הן make_set ו find_sey ו union על קבוצות מופרדות.
e=(u,v) תייצר מעגל אמ״מ find_set(u)=find_set(v) .

MIST-Kruskal(G=(V,E),w)
	E_T = empty_set
	for each u in V
		make set(u)
	sort(E by w)
	for each e = (u,v) in E 
		if find_set(u) != find_set(v)
			E_T.add(e = (u,v))
			union(u,v)
	return E_T		 

Pasted image 20221126230322.png|450

זמן ריצה
במבנה נתונים union_find פעולת make_set לוקחת O(1) זמן ופעולת find_set ופעולת union לוקחות O(α|V|) זמן ממוצע, כאשר α|V| היא inverse Ackerman funcion . האלגוריתם ממיין את הקשתות של E ואז מבצע 2|E| פעולות find ו |V|1 פעולות union. סה״כ

O(sort(E)+|E|α|V|)

כלומר זה תלוי כמה זמן לוקח למיין את E.