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

דוגמה למסלולים זולים לא-ייחודיים עריכה

שאלה עריכה

נתון גרף מכוון  , צומת  ,וטבלת מחירים חיוביים לקשתות  . בשאלה זו אנו מתעניינים במסלול הזול ביותר מ-s לכל צומת  .

א' עריכה

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

ב' עריכה

אנא מצאי אלגוריתם יעיל המקבל את הגרף, צומת המוצא, וטבלת העלויות, ומחזיר מערך בוליאני Unique. Unique[u] יכיל את הערך True אמ"ם יש מסלול יחידי מ  ל . אנא הוכיחי את נכונות האלגוריתם ונתחי סיבוכיותו.

ג' עריכה

אנא מצאי אלגוריתם יעיל המקבל את הגרף, צומת המוצא, וטבלת העלויות, ומחזיר מערך Shortest-Cheapest. ּShortest-Cheapest[u] יכיל את מספר הצמתים במסלול הקצר ביותר מבין המסלולים הזולים ביותר מ  ל . אנא הוכיח את נכונות האלגוריתם ונתחי סיבוכיותו.

תשובה עריכה

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

קביעת מסלולים זולים לא-ייחודיים עריכה

שאלה עריכה

מבני נתונים ואלגוריתמים - מחברת קורס/גרפים/אלגוריתם Dijkstra/תרגילים/קביעת מסלולים זולים לא-ייחודיים/שאלה

תשובה עריכה

מבני נתונים ואלגוריתמים - מחברת קורס/גרפים/אלגוריתם Dijkstra/תרגילים/קביעת מסלולים זולים לא-ייחודיים/תשובה

מסלולים זולים קצרים ביותר עריכה

שאלה עריכה

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

תשובה עריכה

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


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

שאלה עריכה

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

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

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

תשובה עריכה

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

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

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



דוגמה:

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

 
הפיכת Dijkstra לBFS.


המשפט הבא טריביאלי:



משפט:

לכל  , מחיר המסלול הזול מ  ל  ב  הוא מרחק המסלול הקצר מ  ל  ב .


קל גם להווכח שסיבוכיות בניית   היא  . כעת נריץ את אלגוריתם [[מבני נתונים ואלגוריתמים - מחברת קורס/#|]] על  . הסיבוכיות הכוללת (של הבניה ושל ההרצה) במקרה הגרוע היא  .


מציאת מספר מסלולים שונים עריכה

שאלה עריכה

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


 

שימו לב:

הפלט של האלגוריתם הוא פשוט מספר שלם לא-שלילי.

תשובה עריכה

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


Dijkstra עם המסלולים הזולים ביותר עריכה

שאלה עריכה

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

 
בעיית המסלול הזול ביותר.


אז קטע הקוד הבא מדגים שימוש אפשרי:

1		Min-Costs = Dijkstra(G, Edge-Costs, 1)

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

	# Prints 6.
3	Print( Min-Costs[3] )

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

אם הגרף הוא הגרף שראינו לעיל, אז קטע הקוד הבא מדגים שימוש אפשרי בגרסה החדשה:

Print-Path(Path)
1	for i in [1, ..., Length(Path)]
2		Print Path[i]



1	(Min-Costs, Min-Paths) = Dijkstra(G, 1)

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

	# Prints nothing
3	Print-Path( Min-Paths[1] )

	# Prints 6
4	Print( Min-Costs[3] )

	# Prints (1, 6), (6, 7), (7, 3)
5	Print-Path( Min-Paths[3] )
		
	# Prints 9
6	Print( Min-Costs[4] )

	# Prints (1, 6), (6, 7), (7, 3), (3, 4)
7	Print-Path( Min-Paths[4] )

אנא כתוב את השינויים המתאימים ונתח את הסיבוכיות.

תשובה עריכה

ראשית נכתוב פונקציה דומה למה שהתבקשנו לעשות, Dijkstra'. פונקציה זו תחזיר זוג מערכים:

  1. המערך הראשון הוא Min-Costs, שממפה כל צומת u לעלות המסלול הזול ביותר מs לu.
  2. המערך הראשון הוא Parents, שממפה כל צומת u

לצומת הקודם במסלול הזול ביותר מs לu.להלן הפסוודו-קוד המתאים (השינויים מודגשים כך):

Dijkstra'(G, s)
1	pq = Make-Priority-Queue()
2	'''Parents = Make-Array( Length(V(G)) )'''

3	Min-Costs = Make-Array( Length(V(G)) )

4	for u in V(G)
5		Min-Costs[u] = u == s? 0 : 
6		'''Parents[u] = Nil'''
7		Insert(pq, u)
		
8	while Size(pq) > 0
9		u = Delete(pq)

10		for v in A(g, u)
11			if Min-Costs[v] > Min-Costs[u] + Edge-Costs[ (u, v) ]
12				Min-Costs[v] = Min-Costs[u] + Edge-Costs[ (u, v) ]
13	        	'''Parents[v] = u'''
14				Decrease-Key(pq, v)
	                	
15	return '''(Min-Dists, Parents)'''

התוספות פשוטות למדי. מייצרים מערך Parents, בו כל צומת שומר את "אביו" במסלול מs אליו. בכל פעם שצומת מעדכן את שכניו, מעדכנים שהוא אביהם של הצמתים המעודכנים.


נכתוב גם פונקציה Parents-To-Paths ההופכת את מערך האבות Parents למערך מסלולים.

Parents-To-Path(s, v, Parents)
1	stk = Make-Stack()
	
2	while Parents[v] != Nil
3		u = Parents[v]
4		Push(stk, (u, v))
5		v = u

6	Path = Make-Array( Size(stk) )

7	i = 1
8	while Size(stk) > 0
9		Path[i++] = Pop(stk)

10	return Path

הפונקציה מקבלת את צומת המוצא s, צומת כלשהו v, והמערך Parents; היא מחזירה מערך המתאר הת המסלול מs לv.‏ 1-6 "מטיילות" אחורה על מסלול האבות, ומכניסות קשתות למחסנית. 7-11 ממירות את המחסנית למערך, ומחזירות את המערך.

כעת, בעזרת Dijkstra' וParents-To-Paths, נוכל לכתוב את הפונקציה המבוקשת:

Dijkstra(G, s)
1	(Min-Costs, Parents) = Dijkstra'(G, s)

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

3	for v in V(G)
4		Min-Paths[u] = Parents-To-Paths(s, v, Parents)
	
5	return (Min-Costs, Min-Paths)

כעת ננתח את הסיבוכיות:

  1. מהתבוננות בDijkstra', קל לראות שהסיבוכיות שלה הנה זו של אלגוריתם Dijkstra.
  2. Parents-To-Paths היא  .הסיבוכיות הכוללת, לכן, היא  .


Dijkstra על מסלולים אדומים-שחורים עריכה

שאלה עריכה

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

אנא תאר אלגוריתם כזה, ונתח את סיבוכיותו.




דוגמה:

בגרף הבא, הקשתות המודגשות שחורות, וצומת המוצא הוא 1.


 
הבעייה.


בגרף זה, המסלול הקביל הזול ביותר מ1 ל4 הוא  , ועלותו 14. שים לב שהמסלול   אמנם זול יותר, אך פשוט אינו קביל. באותו אופן, אין מסלול קביל מ1 ל6, ולכן עלות 6 היא  .


תשובה עריכה

נבנה גרף חדש כך.

ראשית, נקח את צמתי הגרף  , ונשכפל אותם 3 פעמים, בקבוצות  ,‏  , ו . לכל צומת   בקבוצה  , יש צומת  ‏ ב ,‏ וצומת ‏   ‏ ב .‏ התרשים הבא מראה זאת.

 
שכפול הצמתים.

כעת נוסיף גם קשתות:

  1. כל קשת אדומה בין   ל  (בגרף המקורי) - נמתח אותה בגרף החדש בין   ב  לבין הצומת המתאים ל  ב .
  2. כל קשת שחורה בין   ל  (בגרף המקורי) - נמתח אותה בגרף החדש בין הצומת המתאים ל  ב  לבין הצומת המתאים ל  ב .


התרשים הבא מראה זאת.

 
הוספת הקשתות.

בנוסף נמתח עוד שני סוגי קשתות:

  1. לכל צומת   ב , נמתח קשת במחיר 1 בין   ב  לבין   ‏ ב .
  2. לכל צומת   ב , נמתח קשת במחיר 1 בין   ב  לבין   ‏ ב .(לא נצייר קשתות אלו מטעמי נוחות.)

נשים לב שאם כל הגרפים הנ"ל מיוצגים ברשימת שכנויות, אז סיבוכיות הבניה היא  .

המשפט הבא מסביר את חשיבות הגרף החדש.



משפט:

מחיר המסלול הקביל הזול ביותר מ  ל  בגרף המקורי, קטן בדיוק ב1 ממחיר המסלול הזול ביותר מ  ל  ב  בגרף החדש.



הוכחה: מבניית הגרף החדש, ברור שכל מסלול מ  ל  ב  בגרף החדש, מורכב משני חלקים:

  1. מסלול זהה לחלוטין למסלול קביל מ  ל  בגרף המקורי
  2. מסלול בעלות 1 ל  ב 


 

לכן, כל שנותר הוא להריץ את אלגוריתם Dijkstra.

קל לראות שהסיבוכיות הכוללת היא זו של אלגוריתם Dijkstra.


Dijkstra עם העדפה למסלולים שארכם זוגי עריכה

שאלה עריכה

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

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

תשובה עריכה

נניח שזהו הגרף המקורי:

 
בעיית המסלול הזול ביותר.

נבנה גרף חדש כך.


ראשית, נקח את צמתי הגרף  , ונשכפל אותם 3 פעמים, בקבוצות  ,‏  , ו . לכל צומת   בקבוצה  , יש צומת  ‏ ב ,‏ וצומת ‏   ‏ ב .‏ התרשים הבא מראה זאת.

 
שכפול הצמתים.


כעת נוסיף גם קשתות:

  1. כל קשת בין   ל  (בגרף המקורי) - נמתח אותה בגרף החדש בין   ב  לבין הצומת המתאים ל  ב .
  2. כל קשת בין   ל  (בגרף המקורי) - נמתח אותה בגרף החדש בין הצומת המתאים ל  ב  לבין הצומת המתאים ל  ב .


התרשים הבא מראה זאת (רק חלק מהקשתות מצויירות).

 
הוספת הקשתות.

בנוסף נמתח עוד שני סוגי קשתות:

  1. לכל צומת   ב , נמתח קשת במחיר   בין   ב  לבין   ‏ ב .
  2. לכל צומת   ב , נמתח קשת במחיר   בין   ב  לבין   ‏ ב .(לא נצייר קשתות אלו מטעמי נוחות.)

נשים לב שאם כל הגרפים הנ"ל מיוצגים ברשימת שכנויות, אז סיבוכיות הבניה היא  .


המשפט הבא מסביר את חשיבות הגרף החדש.



משפט:

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



הוכחה: מבניית הגרף החדש, ברור שכל מסלול מ  ל  ב  בגרף החדש, מורכב משני חלקים:

  1. מסלול זהה לחלוטין למסלול מ  ל  בגרף המקורי
  2. מסלול בעלות 1 ל  ב , אם החלק הראשון זוגי, ומסלול בעלות  , אם החלק הראשון אי-זוגי.


 


לכן, כל שנותר הוא להריץ את אלגוריתם Dijkstra.

קל לראות שהסיבוכיות הכוללת היא זו של אלגוריתם Dijkstra.


מסלולים זולים עם קשת יחידה שלילית עריכה

שאלה עריכה

נתונה רשת כבישי אגרה. לכל כביש יש מספר המתאר את עלות הנסיעה בכביש.



דוגמה:

בתרשים הבא, עלות הנסיעה בכביש מ1 ל2 היא  .

 
כבישי אגרה, עם כביש אחד שלילי.


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



דוגמה:

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



 

שימו לב:

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

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



דוגמה:

בתרשים הקודם, נניח שצומת המוצא הוא  :

  1. המסלול הזול ביותר מ  ל6 הוא  ,‏ ועלותו 2.
  2. המסלול הזול ביותר מ  ל4 הוא  ,‏ ועלותו 3.


תשובה עריכה

 

שימו לב:

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

נניח שזהו הגרף המקורי:

 
הבעיה המקורית.

נבנה גרף חדש כך.

ראשית, נקח את צמתי הגרף  , ונשכפל אותם 3 פעמים, בקבוצות  ,‏  , ו . לכל צומת   בקבוצה  , יש צומת  ‏ ב ,‏ וצומת ‏   ‏ ב .‏ התרשים הבא מראה זאת.

 
שכפול הצמתים.

כעת נוסיף גם קשתות:

  1. במקום כל קשת   בגרף המקורי, למעט הקשת השלילית, נמתח קשת מהצומת המתאים ל  ב  לצומת המתאים ל  ב .
  2. במקום כל קשת   בגרף המקורי, למעט הקשת השלילית, נמתח קשת מהצומת המתאים ל  ב  לצומת המתאים ל  ב .
  3. במקום הקשת השלילית   (בגרף המקורי), נמתח קשת מהצומת המתאים ל  ב  לצומת המתאים ל  ב  במחיר 0. התרשים הבא מראה זאת.
 
הוספת הקשתות.

נניח שלקשת השלילית היה מחיר  . בנוסף נמתח עוד שני סוגי קשתות:

  1. לכל צומת   ב , נמתח קשת במחיר   בין   ב  לבין   ‏ ב .
  2. לכל צומת   ב , נמתח קשת במחיר 0 בין   ב  לבין   ‏ ב .(לא נצייר קשתות אלו מטעמי נוחות.)

נשים לב שאם כל הגרפים הנ"ל מיוצגים ברשימת שכנויות, אז סיבוכיות הבניה היא  .

המשפט הבא מסביר את חשיבות הגרף החדש.



משפט:

מחיר המסלול הזול ביותר מ  ל  בגרף המקורי, קטן בדיוק ב  ממחיר המסלול הזול ביותר מ  ל  ב  בגרף החדש.



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

 

כעת כל הקשתות אינן שליליות, ומותר להריץ את Dijkstra.

קל לראות שהסיבוכיות הכוללת היא זו של Dijkstra.