שתי תוכניות נותנות אותה תשובה, אבל אחת מסיימת מיד והשנייה רצה דקות. מה ההבדל? סופרים צעדים, לא שניות, ושואלים: מה קורה כשהקלט גדל? כאן משווים ארבע תוכניות קטנות: O(1), O(n), O(n²), O(log n). כדאי להכיר קודם את חיפוש ומיון, לולאה כפולה ורקורסיה.
n הוא גודל הקלט: כמה איברים ברשימה, כמה תלמידים, כמה שורות. סיבוכיות היא התשובה לשאלה "אם n גדל פי 10, כמה יותר צעדים?". מסמנים אותה ב-O גדולה.
O(1): מספר קבוע של צעדים, לא משנה כמה גדול n. למשל nums[0]. O(n): לולאה אחת על כל האיברים. פי 10 איברים, פי 10 צעדים.
לולאה בתוך לולאה, שתיהן עד n. על כל איבר עוברים שוב על כולם. פי 10 איברים, פי 100 צעדים. עם 1000 איברים זה כבר מיליון.
בכל צעד הבעיה נחצית. מ-1000 מגיעים ל-1 בערך ב-10 צעדים. כך עובד חיפוש בינארי, ולכן הוא כל כך מהיר.
| n | O(1) | O(log n) | O(n) | O(n²) |
|---|---|---|---|---|
| 10 | 1 | 3 | 10 | 100 |
| 10³ | 1 | 9 | 10³ | 10⁶ |
| 10⁶ | 1 | 19 | 10⁶ | 10¹² |
כל אחת מארבע התוכניות סופרת את הצעדים שלה במשתנה steps. הזיזו את n וראו איך הפסים משתנים. בחרו תוכנית למעקב, ולחצו "שורה הבאה" או "עד הסוף": הפס שלה מתמלא בזמן הריצה.
נסו את אלה:
אפשר לשנות את הקוד ולהריץ פייתון אמיתי. בפעם הראשונה הטעינה לוקחת כמה שניות.
לולאה ואחריה עוד לולאה זה n + n = 2n צעדים, כלומר O(n). רק לולאה בתוך לולאה נותנת O(n²).
✓ O(n), לא O(n²)for x in nums:
print(x)
for x in nums:
print(x * 2)
אם הלולאה הפנימית רצה תמיד 3 פעמים, זה 3n, כלומר O(n). שואלים: האם גם היא גדלה עם n?
✓ O(n)for x in nums:
for k in range(3):
print(x + k)
x in nums, nums.index(x), sum(nums) ו-max(nums) עוברים על כל הרשימה. בתוך לולאה הם הופכים אותה ל-O(n²).
for x in nums:
if x * 2 in nums:
print(x)
זמן בשניות תלוי במחשב. סיבוכיות סופרת צעדים, ולכן היא נכונה בכל מחשב. מחשב מהיר פי 10 לא מציל O(n²) כש-n גדל פי 1000.
✓ סופרים צעדיםsteps = 0
for x in nums:
steps = steps + 1
print(steps) # n
מחפשים את 56 ברשימה ממוינת של 8 מספרים, בחיפוש לינארי: בודקים אחד אחד מההתחלה. 56 הוא האחרון, וזה המקרה הגרוע ביותר. סופרים כמה בדיקות צריך.
מדד לכמה צעדים אלגוריתם עושה, כתלות בגודל הקלט n. כותבים אותה ב-O גדולה: O(1) קבוע, O(log n) לוגריתמי, O(n) לינארי, O(n²) ריבועי. היא אומרת מה קורה כש-n גדל, ולא כמה שניות התוכנית רצה. נסו במעבדה.
כי בכל בדיקה הוא זורק חצי מהרשימה. מ-1000 נשארים 500, 250, 125 וכך הלאה, ואחרי כ-10 בדיקות נשאר איבר אחד. חיפוש לינארי צריך עד 1000 בדיקות לאותה רשימה.
מסתכלים על הלולאות. לולאה אחת עד n: O(n). לולאה בתוך לולאה, שתיהן עד n: O(n²). n שנחצה בכל סיבוב: O(log n). בלי לולאות בכלל: O(1). ושימו לב לפעולות כמו in על רשימה, שהן לולאה מוסתרת. תרגלו למטה.