מירי
כיתה י · מבני נתונים ואלגוריתמים

סיבוכיות זמן כמה צעדים?

שתי תוכניות נותנות אותה תשובה, אבל אחת מסיימת מיד והשנייה רצה דקות. מה ההבדל? סופרים צעדים, לא שניות, ושואלים: מה קורה כשהקלט גדל? כאן משווים ארבע תוכניות קטנות: O(1), O(n), O(n²), O(log n). כדאי להכיר קודם את חיפוש ומיון, לולאה כפולה ורקורסיה.

1

הרעיון

n הוא גודל הקלט: כמה איברים ברשימה, כמה תלמידים, כמה שורות. סיבוכיות היא התשובה לשאלה "אם n גדל פי 10, כמה יותר צעדים?". מסמנים אותה ב-O גדולה.

O(1) ו-O(n)

O(1): מספר קבוע של צעדים, לא משנה כמה גדול n. למשל nums[0]. O(n): לולאה אחת על כל האיברים. פי 10 איברים, פי 10 צעדים.

O(n²)

לולאה בתוך לולאה, שתיהן עד n. על כל איבר עוברים שוב על כולם. פי 10 איברים, פי 100 צעדים. עם 1000 איברים זה כבר מיליון.

O(log n)

בכל צעד הבעיה נחצית. מ-1000 מגיעים ל-1 בערך ב-10 צעדים. כך עובד חיפוש בינארי, ולכן הוא כל כך מהיר.

nO(1)O(log n)O(n)O(n²)
101310100
10³1910³10⁶
10⁶11910⁶10¹²
לזכור: מה שקובע הוא הצורה, לא המספרים הקטנים. 3n ו-n + 5 הם שניהם O(n). אבל ההבדל בין O(n) ל-O(n²) הוא ההבדל בין שנייה לשבוע.
2

מעבדה: סופרים צעדים

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

נסו את אלה:

הריצו בעצמכם

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

3

טעויות שכולם עושים

שתי לולאות זו אחרי זו

לולאה ואחריה עוד לולאה זה 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²).

✗ נראה O(n), בפועל 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
4

דוגמה פתורה: כמה צעדים לחיפוש?

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

5

תרגול

6

שאלות שכולם שואלים

מה זה סיבוכיות זמן?

מדד לכמה צעדים אלגוריתם עושה, כתלות בגודל הקלט n. כותבים אותה ב-O גדולה: O(1) קבוע, O(log n) לוגריתמי, O(n) לינארי, O(n²) ריבועי. היא אומרת מה קורה כש-n גדל, ולא כמה שניות התוכנית רצה. נסו במעבדה.

למה חיפוש בינארי הוא O(log n)?

כי בכל בדיקה הוא זורק חצי מהרשימה. מ-1000 נשארים 500, 250, 125 וכך הלאה, ואחרי כ-10 בדיקות נשאר איבר אחד. חיפוש לינארי צריך עד 1000 בדיקות לאותה רשימה.

איך מזהים סיבוכיות של קוד?

מסתכלים על הלולאות. לולאה אחת עד n: O(n). לולאה בתוך לולאה, שתיהן עד n: O(n²). n שנחצה בכל סיבוב: O(log n). בלי לולאות בכלל: O(1). ושימו לב לפעולות כמו in על רשימה, שהן לולאה מוסתרת. תרגלו למטה.