אלגוריתם פלויד-וורשאל

אלגוריתם פלויד-וורשאל הוא אלגוריתם במדעי המחשב המשמש למציאת המסלולים הקצרים (או הקלים) ביותר בין כל שני צמתים, בגרף ממושקל ומכוון. האלגוריתם מבוסס על פרדיגמת התכנון הדינמי. האלגוריתם פועל גם על גרפים שמכילים קשתות עם משקלים שליליים, אבל שלא מכילים מעגל בעל משקל שלילי (בניגוד לאלגוריתם דייקסטרה שמניח קשתות אי שליליות). אם בגרף יש מעגל עם משקל שלילי האלגוריתם יגלה זאת. סיבוכיות זמן הריצה של האלגוריתם היא <math>\ \Theta(V^3)</math>.

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

==תיאור האלגוריתם==

האלגוריתם מתבסס על האבחנה הבאה: אם ממספרים את קבוצת הצמתים של הגרף כך שהיא מסומנת <math>\ \left\{1,2,3,\dots,n\right\}</math> ומסמנים בתור <math>\ d_k(x,y)</math> את משקל המסלול הקצר ביותר מהצומת <math>\ x</math> אל הצומת <math>\ y</math> שעובר בצומתי ביניים השייכים אך ורק לקבוצה <math>\ \left\{1,2,3,\dots,k\right\}</math>, אז מתקיים: <math>\ d_k(x,y)=\min\left\{d_{k-1}(x,y),d_{k-1}(x,k)+d_{k-1}(k,y)\right\}</math>.

הסבר: אם קיים מסלול קצר ביותר מ-<math>\ x</math> אל <math>\ y</math> שעובר בצמתים שמספרם לכל היותר <math>\ k</math> לא עובר בצומת שמספרו <math>\ k</math>, אז <math>\ d_k(x,y)=d_{k-1}(x,y)</math>. לעומת זאת, אם כל מסלול קצר ביותר כזה חייב לעבור בצומת שמספרו <math>\ k</math> אז ניתן להניח שהוא עובר בו רק פעם אחת (כי אין מעגלים שליליים בגרף, ולכן אם חוזרים לאותו צומת פעמיים ניתן להשמיט מהמסלול את המעגל מהצומת לעצמו). לכן אפשר לפרק כל מסלול כזה לשני מסלולים: אחד שהולך מ-<math>\ x</math> אל <math>\ k</math>, ושני שהולך מ-<math>\ k</math> אל <math>\ y</math>. שני המסלולים הללו לא עוברים בצמתי ביניים שמספרם גדול מ-<math>\ k-1</math>, ולכן סכום משקלם נתון על ידי <math>\ ,d_{k-1}(x,k)+d_{k-1}(k,y)</math>. לכן כל מה שנותר הוא לבדוק איזו משתי השיטות עדיפה לכל צומת.

כעת, פעולת האלגוריתם היא זו: הוא מבצע <math>\ |V|</math> איטרציות, כאשר באיטרציה ה-<math>\ k</math> הוא מחשב את <math>\ d_k(x,y)</math> לכל זוג צמתים <math>\ x,y\isin V</math> ושומר את המידע בטבלה. חישוב <math>\ d_k(x,y)</math> מתבצע בזמן <math>\ O(1)</math> בהינתן המידע מהאיטרציה הקודמת, ולכן כל איטרציה של האלגוריתם מתבצעת בזמן <math>\ O(V^2)</math> (מספרם של זוגות הצמתים האפשריים). לכן זמן הריצה הכולל של האלגוריתם יהיה <math>\ O(V^3)</math>.

המחשת האלגוריתם

תהי W מטריצת משקלי הקשתות: כלומר w(i,j) הוא משקל הקשת המחברת את i עם j. (משקל זה הוא אין-סוף אם אין קשת ביניהם).
#<math>\ D_0=\ W</math>
#עבור <math>\ k </math> מ-1 עד <math>\ n </math> בצע:<br />
:2.1. עבור <math>\ x </math> מ-1 עד <math>\ n </math> בצע:
::2.1.1. עבור <math>\ y </math> מ-1 עד <math>\ n </math> בצע:
:::2.1.1.1.<math>\ d_k(x,y)=\min\left\{d_{k-1}(x,y),d_{k-1}(x,k)+d_{k-1}(k,y)\right\}</math>

בסוף, <math>\ D_n</math> היא מטריצת אורך המסלולים הקצרים ביותר שעוברים דרך הצמתים <math>\ \left\{1,2,3,\dots,n\right\}</math>.

פסאודו קוד:<syntaxhighlight lang="text" line="1">
 let dist be a |V| × |V| array of minimum distances initialized to ∞ (infinity)
 for each edge (u,v)
 dist[u][v] ← w(u,v) // the weight of the edge (u,v)
 for each vertex v
 dist[v][v] ← 0
 for k from 1 to |V|
 for i from 1 to |V|
 for j from 1 to |V|
 if dist[i][j] > dist[i][k] + dist[k][j] 
 dist[i][j] ← dist[i][k] + dist[k][j]
 end if
 end-for
 end-for
end-for
</syntaxhighlight>

מעניין לציין שגם בהרצה בסדר "הלא נכון" האלגוריתם עובד כשורה לאחר מספר חזרות, כלומר אם נחליף את השורות:
<syntaxhighlight lang="text" line="1">
 for k from 1 to |V|
 for i from 1 to |V|
 for j from 1 to |V|
 ...
 end-for
 end-for
end-for
</syntaxhighlight>

באחת משתי האפשרויות:

<syntaxhighlight lang="text" line="1">
 for _ from 1 to 3
 for i from 1 to |V|
 for j from 1 to |V|
 for k from 1 to |V|
 ...
 end-for
 end-for
 end-for
end-for
</syntaxhighlight>
<syntaxhighlight lang="text" line="1">
 for _ from 1 to 2
 for i from 1 to |V|
 for k from 1 to |V|
 for j from 1 to |V|
 ...
 end-for
 end-for
 end-for
end-for
</syntaxhighlight>

האלגוריתם ירוץ כשורה

==טיפול במעגלים שליליים==
מעגל שלילי הוא מעגל שסכום קשתותיו קטן מאפס. בין שני קודקודים <math>i, j</math> שמשתתפים במעגל שלילי לא קיים מסלול קצר ביותר, כי בכל מעבר על המעגל נקבל מסלול שמשקלו קטן יותר, וניתן להמשיך כך עד אין-קץ. על כן, במקרה של מעגל שלילי האלגוריתם לא יפיק פלט משמעותי. עם זאת, במידה וקיים מעגל שלילי, ניתן להיעזר באלגוריתם על מנת לגלות זאת:
* בתחילה, המרחק בין כל קודקוד <math>i\in V</math> לעצמו הוא 0.
* מסלול <math>[i, k, ..., i]</math> יכול לשפר, אם ורק אם סכום קשתותיו שלילי, ז"א מדובר במעגל שלילי.
* על כן, אם קיים קודקוד <math>i</math> שמרחקו מעצמו שלילי, משמעות הדבר שקיים לפחות מעגל שלילי אחד בגרף.
מכאן, באמצעות התבוננות במטריצת המסלולים הקצרים ביותר שהפיק האלגוריתם, ניתן להסיק שקיים מעגל שלילי, אם באחד מהתאים על האלכסון הראשי קיים מרחק קטן מאפס.

==ראו גם==
* האלגוריתם של ג'ונסון

==קישורים חיצוניים==

* 

==הערות שוליים==

קטגוריה:אופטימיזציה בדידה
פלויד-וורשאל