מבני נתונים ויעילות אלגוריתמים מכללת אורט כפר-סבא מבני נתונים ויעילות אלגוריתמים חסמים אסימפטוטיים 10.09.14 אורי וולטמן uri.weltmann@gmail.com
חידה לחימום נתון לוח שחמט רגיל (8x8) קל לראות שאפשר לכסות את לוח השחמט בעזרת 32 אבני הדומינו.
חידה לחימום כעת הסירו מהלוח את המשבצת הימנית-העליונה ואת המשבצת השמאלית-התחתונה. האם כעת ניתן לכסות את הלוח באמצעות 31 אבני דומינו?
סיבוכיות זמן ריצה בשיעור הקודם דנו בשאלה כיצד יש לנתח את יעילותם של אלגוריתמים, והגענו למסקנה כי התבוננות בפונקציות זמן הריצה עדיפה על פני דרכים אחרות (כמו הרצת האלגוריתם במחשב ומדידת זמן הריצה בעזרת שעון). ראינו כי אלגוריתם ניתן לשפר בשתי צורות: שיפור בקבוע שיפור בסדר גודל השתמשנו בסימן O ("או גדול") כדי לתאר סדר גודל של אלגוריתמים. למשל: O(n), O(n2), O(1).
תרגיל לפניך רשימה של פונקציות זמן ריצה של אלגוריתמים שונים. קבע עבור כל אחת מהן מהו סדר הגודל שלה: 10n + 5 0.5n4 + 200n3 + 70 7n2 + 5n3 + 20n 100 5n 0.001n + 1000 10000n + 1000 2n2 + 2 2n
סימון אסימפטוטי השתמשנו בסימון O מבלי להגדירו במדויק. בנוסף, נגדיר סימונים נוספים: Ω ("אומגה") , Θ ("תֵטַה"). לכל הסימונים האלה קוראים 'חסמים אסימפטוטיים' ((asymptotic bounds.
הסימון O ראינו מספר פונקציות שהן מסדר גודל O(n): מתי נאמר על פונקציה מסוימת f(n) שהיא מסדר גודל O(n) ? נאמר על פונקציה מסוימת f(n) שהיא מסדר גודל O(n) אם קיימים קבועים חיוביים n0 ו-c כך שלכל n > n0 מתקיים f(n) < c · n
הסימון O האם זו האפשרות היחידה לבחירת הקבועים? נוכיח, לפי ההגדרה, ש-2n+11 הוא אכן מסדר גודל O(n): נבחר בתור קבועים את c = 3 ואת n0 = 11 קל לראות שלכל n > 11 מתקיים 2n+11 < 3n האם זו האפשרות היחידה לבחירת הקבועים? אפשר לקחת c = 13 , n0 = 1 נוכיח לפי ההגדרה ש-1000n+1 מסדר גודל O(n): נבחר c = 1001 , n0 = 1 לכל n > 1 מתקיים 1000n+1 < 1001n f(n) היא מסדר גודל O(n) אם ורק אם קיימים קבועים חיוביים n0 ו- cכך שלכל n > n0 מתקיים f(n) < c · n
הסימון O כשאומרים על פונקציה מסוימת שהיא מסדר גודל O(n), המשמעות היא שיש לה חסם עליון לינארי. כלומר, שקיימת פונקצית זמן ריצה לינארית שעבור ערכי n גדולים, חוסמת אותה מלמעלה. נשים לב שגם פונקציות זמן ריצה קבועות הן מסדר O(n). נוכיח, למשל, שהפונקציה f(n) = 12 היא מסדר גודל O(n). ניקח c = 1 ואת n0 = 12 קל לראות שלכל n > 12 מתקיים 12 < n f(n) היא מסדר גודל O(n) אם ורק אם קיימים קבועים חיוביים n0 ו- cכך שלכל n > n0 מתקיים f(n) < c · n
הסימון O הגדרנו בצורה פורמלית מהן הפונקציות מסדר גודל O(n). הגדרה: נאמר על פונקציה מסוימת f(n) שהיא מסדר גודל O(n2) אם קיימים קבועים חיוביים n0 ו-c כך שלכל n > n0 מתקיים f(n) < c · n2 נוכיח כי 2n2+3n+4 הוא O(n2): ניקח c = 3 , 4 = n0 או c = 5 , 2 = n0 גם 7n+3 היא מסיבוכיות O(n2): ניקח c = 1 , 8 = n0
הסימון O אחרי שהגדרנו באופן פורמלי את O(n) ו-O(n2), ננסה להכליל ולהגדיר O(g(n)), עבור פונקציה כלשהי g(n). הגדרה: נאמר על פונקציה f(n) שהיא מסדר גודל O(g(n)) אם קיימים קבועים חיוביים n0 ו-c כך שלכל n > n0 מתקיים f(n) < c · g(n) למשל: 2n+3 = O(n) 2n+3 = O(n2) n4+3n2+2n+2=O(n4) 5 = O(1) 5 = O(n4)
הסימון Ω ראינו כי O(g(n)) מגדיר חסם אסימפטוטי עליון על פונקצית זמן ריצה של אלגוריתם. הסימון Ω(g(n)) מגדיר, באופן שקול, חסם אסימפטוטי תחתון. הגדרה: נאמר על פונקציה f(n) שהיא מסדר גודל Ω(g(n) אם קיימים קבועים חיוביים n0 ו-c כך שלכל n > n0 מתקיים f(n) > c · g(n) למשל: 2n+3 = Ω(n) 2n+3 = Ω(1) 5n2+1000 = Ω(n2) n4+3n2+2n+2= Ω(n4) n4+3n2+2n+2= Ω(n2)
תכונות של חסמים אסימפטוטיים משפט: לכל שתי פונקציות f(n) ו-g(n), מתקיים . f(n) = O(g(n))אם ורק אם g(n) = Ω(f(n)). ברור גם שמתקיים: אם f(n) = O(g(n)) וגם g(n) = O(h(n)) אז f(n) = O(h(n)) אם f(n) = Ω(g(n)) וגם g(n) = Ω(h(n)) אז f(n) = Ω(h(n)) וכמו כן מתקיים: לכל פונקציה f(n) מתקיים f(n) = O(f(n)) לכל פונקציה f(n) מתקיים f(n) = Ω(f(n))
הסימון Θ מתקיים 3n2+5n+2 = O(n2) וגם 3n2+5n+2 = Ω(n2) . כלומר, הביטוי n2 מהווה גם חסם אסימפטוטי עליון וגם חסם אסימפטוטי תחתון עבור הפונקציה 3n2+5n+2. במקרה כזה, נאמר שהוא חסם אסימפטוטי הדוק ונסמנו ב-Θ. הגדרה: נאמר על פונקציה f(n) שהיא מסדר גודל Θ(g(n)) אם קיימים קבועים חיוביים n0 , c1 , c2 כך שלכל n > n0 מתקיים c2 · g(n) > f(n) > c1 · g(n) כלומר, הפונקציה f(n) היא Θ(g(n)) אם היא מתנהגת כמו g(n). חסומה מלמעלה על-ידי כפולה של g(n) חסומה מלמטה על-ידי כפולה של g(n)
תכונות של חסמים אסימפטוטיים משפט: לכל שתי פונקציות f(n) ו-g(n), מתקיים f(n) = Θ(g(n)) אם ורק אם f(n) = Ω(g(n)) וגם f(n) = O(g(n)) . תכונות החסם האסימפטוטי ההדוק: אם f(n) = Θ(g(n)) וגם g(n) = Θ(h(n)) אז f(n) = Θ(h(n)) לכל פונקציה f(n) מתקיים f(n) = Θ(f(n)) f(n) = Θ(g(n)) אם ורק אם g(n) = Θ(f(n))
תזכורת - לוגריתם פונקציה מתמטית שימושית המוכרת לנו מהתיכון, היא פונקצית הלוגריתם (logarithm), המוגדרת כך: loga(b) = c אם ורק אם ac=b קרי: הלוגריתם של b בבסיס a שווה ל-c אם"ם a בחזקת c שווה ל-b. כלומר, מדובר כאן בסוג של פעולה הפוכה להעלאה בחזקה. דוגמאות: log2(8) = log2(32) = log2(1024) = log10(10) = log10(100) = log10(1000) = log3(81) = log4(64) = log5(25) = עבור כל בסיס a מתקיים: loga(1) = loga(a) = 3 5 10 1 2 3 4 3 2 1
לוגריתם חוקי הלוגריתמים הבאים, מתקיימים עבור כל a,b,c ממשיים חיוביים (בתנאי שבסיס הלוגריתם גדול מ-1):
לוגריתם הגרפים הללו נבדלים ביניהם רק בכפל בקבוע. אם נשרטט את הגרף של פונקציות לוגריתם בעלות בסיסים שונים, נגלה כי קיבלנו גרפים הדומים זה לזה בהתנהגותם. log10(x) log3(x) log2(x) log1.5(x) הגרפים הללו נבדלים ביניהם רק בכפל בקבוע. על כן, כל הפונקציות הללו הן מאותו סדר גודל: (logn)Θ
לוגריתם נוהגים לכתוב (logn)Θ, מבלי לציין את הבסיס של הלוגריתם, שכן מלוגריתם בבסיס מסוים ניתן לעבור ללוגריתם בכל בסיס אחר, על-ידי כפל במספר קבוע. הנוסחא בה משתמשים: דוגמא לשימוש בנוסחא: log8(32) = log2(32) / log2(8) = 5 / 3 = 1.66 הנוסחא מאפשרת לנו, למשל, לעבור מהפונקציה log2(n) ל- log10(n) על-ידי חלוקה בקבוע log2(10). באופן דומה, ניתן לעבור מכל loga(n) לכל logb(n). על כן, כל הפונקציות הללו הן מאותו סדר גודל, וזו הסיבה מדוע כותבים פשוט (logn)Θ.
לוגריתם פונקציה לוגריתמית גדלה בקצב איטי, ולכן, באופן כללי, נעדיף אלגוריתם שפונקצית זמן הריצה שלו היא לוגריתמית, על פני אלגוריתם שפונקצית זמן הריצה שלו היא לינארית.
מהי סיבוכיות זמן הריצה של קטע קוד זה? לפניך קטע קוד המסכם שני וקטורים (מערכים חד-מימדיים) a ו-b, לתוך מערך חד-מימדי c: מהי סיבוכיות זמן הריצה של קטע קוד זה? לפניך קטע קוד המסכם שתי מטריצות (מערכים דו-מימדיים) a ו-b, לתוך מערך דו-מימדי c: for (i = 0; i < n; i++) c[i] = a[i] + b[i]; for (i = 0; i < n; i++) for (j = 0; j < n; j++) c[i][j] = a[i][j] + b[i][j];
מהי סיבוכיות זמן הריצה של קטע הקוד? לפניך קטע קוד: מהי סיבוכיות זמן הריצה של קטע הקוד? c = n; while (c > 1) c = c / 2;
מתקיים כלומר, הלולאה מתבצעת log2(n) פעמים.
תרגיל לפניך קטע קוד: מהי סיבוכיות זמן הריצה של קטע קוד זה? חישוב דומה מאוד לחישוב הקודם, יראה כי הלולאה מתבצעת log2(n) פעמים, ולכן סיבוכיות זמן הריצה של קטע קוד זה היא, כמו במקרה הקודם, מסדר גודל (logn)Θ. שאלה: מדוע אנו אומרים פשוט כי הסיבוכיות של קטע הקוד היא לוגריתמית, ולא מציינים מהו בסיס הלוגריתם? c = 1; while (c < n) c = c * 2;
תרגיל (אביב תשס"ג) לפניך קטע קוד: נוסף על קטע הקוד נתון: S1, S2 ו-S3 אינם משנים את ערכם של: m, n, k, j, i ו-t. S1, S2 ו-S3 הם משפטים פשוטים, והזמן הדרוש לביצועם הוא O(1). מהי סיבוכיות זמן הריצה של קטע הקוד הנתון? O(n · (m + t)) O(n · m · t) O(n + m + t) O(n + m · t) for (i = 1; i <= n; i++) { S1; for (j = 1; j <= m; j++) S2; for (k = 1; k <= t; k++) S3; }
תרגיל (אביב תשס"ב) O(max(n,m,t)) O(n · m + n · t) O(n · m · t) לפניך קטע קוד: נתון כי: S1, S2 ו-S3 אינם משנים את ערכם של: m, n, k, j, i ו-t. S1, S2 ו-S3 הם משפטים פשוטים, והזמן הדרוש לביצועם הוא O(1). מהי סיבוכיות זמן הריצה של קטע הקוד הנתון? O(max(n,m,t)) O(n · m + n · t) O(n · m · t) אף תשובה אינה נכונה for (i = 1; i <= n; i++) { S1; for (j = 1; j <= m; j++) { S2; for (k = 1; k <= t; k++) S3; }
תרגיל (אביב תשס"ב) O(n/2) O(1) O(logn) אף תשובה אינה נכונה נתון קטע הקוד הזה: נתון כי: S אינו משנה את הערכים של: c, i ו-n. S הוא משפט פשוט והזמן הדרוש לביצועו הוא O(1). מהי סיבוכיות זמן הריצה של קטע הקוד הנתון? O(n/2) O(1) O(logn) אף תשובה אינה נכונה c = n; while (c > 1) { for (i = 1; i <= 10000; i++) S; c = c / 2; }
תרגיל (אביב תשס"ה) Θ(n3) Θ(n2) Θ(n · logn) Θ(n2 · logn) לפניך קטע קוד: כמו כן נתון כי: S הוא משפט פשוט, והזמן הדרוש לביצועו הוא Θ(1). S אינו משנה את הערכים של: n, k, j, i. כל המשתנים בקטע הם מטיפוס שלם. סיבוכיות זמן הריצה של קטע הקוד הנתון כפונקציה של n היא: Θ(n3) Θ(n2) Θ(n · logn) Θ(n2 · logn) for (i = 1; i <= n; i++) for (j = 1; j <= 10; j++) for (k = n; k <= n+5; k++) { x = n; while (x > 1) { x = x / 2; S; } }
תרגיל נתון קטע הקוד הבא: הניחו ש-S הוא משפט פשוט, אשר הזמן הדרוש לביצועו הוא קבוע . כמו כן, הניחו כי S אינו משנה את הערכים של n , j , i. מהי סיבוכיות זמן הריצה של קטע קוד זה? פתרון: כאשר i = 0 , מתבצעות n איטרציות. כאשר i = 1 , מתבצעות n-1 איטרציות. כאשר i = 2 , מתבצעות n-2 איטרציות. ... כאשר i = n-1 , מתבצעות איטרציה אחת. for (i = 0; i < n; i++) for (j = i; j < n; j++) S;
תזכורת – סדרה חשבונית כזכור, סדרה חשבונית (arithmetic sequence), היא סדרת מספרים שבה ההפרש בין כל שני מספרים עוקבים הוא קבוע. למשל, הסדרות הבאות הן סדרות חשבוניות: ... , 28 , 22 , 16 , 10 , 4 ... , 11 , 7 , 3 , 1- ... , 5- , 2- , 1 , 4 , 7 , 10 את האיבר הראשון בסדרה מקובל לסמן ב- a1 . את ההפרש הקבוע בין שני איברים עוקבים בסדרה מקובל לסמן ב- d . הנוסחא לאיבר הכללי בסדרה (האיבר שנמצא במקום ה-n-י) היא: an= a1+ (n-1)·d
תזכורת – סדרה חשבונית את הנוסחא לסכום n האיברים הראשונים בסדרה חשבונית, ניתן לרשום בכמה דרכים: