מבני נתונים ואלגוריתמים - מחברת קורס/אלגוריתמים/מציאת סיבוכיות פסוודו-קוד
תבנית:מבני נתונים ואלגוריתמים - מחברת קורס
בסדרי גדילה למדנו לסווג פונקציות (מתמטיות) לקבוצות סדרי הגדילה, ובנוסחאות נסיגה למדנו כיצד לפתור את סדר הגדילה של פונקציה (מתמטית) הנתונה על ידי נוסחת נסיגה. בדף זה נלמד כיצד לתרגם פסוודו-קוד לסדרי גדילה (לעתים ישירות ולעתים לנוסחאות נסיגה שאותן נפתור כפי שלמדנו).
פרימיטיבים
פקודות מסויימות פשוטות כ"כ שברור שכ"א אורכת . להלן מספר דוגמות:
Print( "Hello" )
a = 3
if i == j
פעולות חשבוניות
אלא אם יצויין אחרת, נניח שהפעולות החשבוניות הבסיסיות חיבור, חיסור, כפל, ושארית, כולן אורכות גם הן. להלן מספר דוגמות:
++b
mid = ⌊(left + right) / 2⌋
קריאות לפונקציות
נניח שאנו רואים בקוד את הפקודה תבנית:קוד בשורה. הפונקציה תבנית:קוד בשורה אורכת זמן כלשהו, אך כמה עולה הקריאה עצמה? חשוב להבין שהקריאה עצמה אורכת זמן.
מצד אחד, כל פקודה אורכת זמן כלשהו, והדבר נכון גם לקריאה לפונקציה. אפילו אם הפוקנציה תבנית:קוד בשורה אינה עושה כלום, הקריאה לה עולה משהו. מצד שני, היות שתבנית:קוד בשורה מקבלת מספר ארגומנטים קבוע, הקריאה לה חסומה מלמעלה על ידי זמן כלשהו. גם אם אחד הארגומנטים הוא מערך (כמו תבנית:קוד בשורה, כאן, לדוגמה), הוא אינו מועתק. לכן עצם הקריאה חסומה מלמעלה על ידי קבוע שאינו תלוי בגודל הבעיה.
רצפים
כל רצף של פקודות עולה סך הזמן של כל אחת מהפקודות. לדוגמה, אם תבנית:קוד בשורה עולה (כאשר הוא תבנית:קוד בשורה), אז קטע הקוד הבא אורך .
1 a = 3
2 Foo(a, B, 3)
3 if j == 1
4 print "Hello"
1, 3, ו4 אורכות כל אחת , ו2 אורכת . סה"כ נקבל (עפ"י אדיטיביות).
לולאות
נתבונן בלולאה בקטע הקוד הבא.
1 for i in [1, …, n]
2 Foo(a, B, 3)
ברור לחלוטין ש2 מתבצעת פעמים. לכן, אם תבנית:קוד בשורה היא , אז 2 תורמת לסיבוכיות, ואם תבנית:קוד בשורה היא , אז 2 תורמת לסיבוכיות.
מה סיבוכיות שתי השורות, 1-2? במקרה הראשון גם כן , ובמקרה השני גם כן . 1 עולה : ככלות הכל, בשפת C, היינו כותבים אותה כך:
for(i = 1; i <= n; ++i)
לכן במקרה הראשון הסיבוכיות היא , ובמקרה השני .
באופן דומה, נתבונן בלולאה הכפולה בקטע הקוד הבא.
1 for i in [1, …, n]
2 for j in [1, …, m]
3 Foo(a, B, 3)
ברור ש2 מתבצעת פעמים, ו3 מתבצעת פעמים. לכן אם תבנית:קוד בשורה היא , אז הסיבוכיות היא ואם תבנית:קוד בשורה היא , אז הסיבוכיות היא
לסיום, נתבונן בלולאה הכפולה בקטע הקוד הבא.
1 for i in [1, …, n]
2 for j in [1, …, f(i)]
3 Print( "Hello" )
כאן היא פונקציה מתמטית כלשהי המתרגמת מספרים שלמים חיוביים למספרים שלמים חיוביים. ודא שהנך יכול להראות שהסיבוכיות היא .
פונקציות רקורסיביות רגילות
להלן דוגמה לפונקציה רקורסיבית.
Foo(n)
1 if n == 1
2 return 1
3 return Foo(n - 1)
כיצד אפשר לנתח זאת? לרוב כדאי לפעול לפי הרעיון הבא. נגדיר את זמן הריצה של תבנית:קוד בשורה בהנתן קלט בגודל כ. בגוף הפונקציה, נחייב כל קריאה לפונקציה לפי הגדרה זאת.
לדוגמה, 3 אורכת . לפי מה שראינו ברצפים, גוף הפונקציה, כאשר הארגומנט הוא , אורך . זהו בדיוק , על פי ההגדרה. קיבלנו את נוסחת הנסיגה .
פונקציות רקורסיביות עם Memoization
נניח שיש לנו פונקציה רקורסיבית עם memoization, תבנית:קוד בשורה, המקבלת כפרמטר גודל . לרוב כדאי לפעול לפי הרעיון הבא. נגדיר את זמן הריצה של תבנית:קוד בשורה בהנתן קלט בגודל , תחת ההנחה שכל קריאה רקורסיבית היא , כ. אז:
- זמן הריצה של הפונקציה על קלט בגודל הוא .
- אם קריאה עם קלט בגודל בהכרח תיקרא רקורסיבית לכל הגדלים בגודל פחות מ, אז זמן הריצה הוא .
(את ההסבר לכך נלמד בבעיית דג הסלמון.)