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

רקורסיה פונקציה שקוראת לעצמה

רקורסיה היא פונקציה שפותרת בעיה בעזרת גרסה קטנה יותר של אותה בעיה. כדי לחשב 4 עצרת, מספיק לדעת 3 עצרת. כדי לדעת 3 עצרת, מספיק לדעת 2 עצרת. וכך הלאה, עד מקרה שקל לפתור לבד. כאן עוקבים אחרי מחסנית הקריאות ורואים מה קורה כששוכחים את מקרה הבסיס. כדאי להכיר קודם פונקציות בפייתון. אחר כך ממשיכים לסיבוכיות.

1

הרעיון

def fact(n):
    if n == 0:            # base case
        return 1
    return n * fact(n - 1)   # recursive step

print(fact(4))            # 24

מקרה בסיס

המקרה הקטן ביותר, שהתשובה שלו ידועה בלי עוד קריאה. אצל עצרת: 0! = 1. בלי מקרה בסיס, הפונקציה קוראת לעצמה בלי סוף.

צעד רקורסיבי

הפונקציה קוראת לעצמה עם בעיה קטנה יותר, ומשתמשת בתשובה: 4! = 4 · 3!. כל קריאה חייבת להתקרב למקרה הבסיס.

מחסנית הקריאות

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

לזכור: לכל פונקציה רקורסיבית שתי שאלות: מה מקרה הבסיס? והאם כל קריאה מתקרבת אליו? אם התשובה לשנייה היא לא, תקבלו RecursionError.
2

מעבדה: מחסנית הקריאות

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

נסו את אלה:

מחסנית הקריאות (למעלה: הקריאה שרצה עכשיו)

    הריצו בעצמכם

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

    3

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

    אין מקרה בסיס

    הפונקציה קוראת לעצמה שוב ושוב. אחרי בערך 1000 קריאות פייתון עוצר אותה עם RecursionError.

    ✗ RecursionError
    def fact(n):
        return n * fact(n - 1)
    ✓ נכון
    def fact(n):
        if n == 0:
            return 1
        return n * fact(n - 1)

    מתרחקים מהבסיס

    יש מקרה בסיס, אבל לא מגיעים אליו. עם n + 1 המספר רק גדל, ו-fact(-1) מדלג מעל 0.

    ✗ לא נגמר
        return n * fact(n + 1)
    print(fact(-1))
    ✓ מתקרבים ל-0
        return n * fact(n - 1)
    print(fact(4))

    שוכחים return

    בלי return, הפונקציה מחשבת ולא מחזירה כלום, כלומר None. הקריאה שמעליה מנסה להכפיל ב-None ונופלת.

    ✗ TypeError
        if n == 0:
            return 1
        n * fact(n - 1)
    ✓ נכון
        if n == 0:
            return 1
        return n * fact(n - 1)

    ערך בסיס לא נכון

    אם מקרה הבסיס מחזיר 0, כל המכפלה מתאפסת. בכפל הבסיס הוא 1, ובחיבור הוא 0.

    ✗ fact(4) = 0
        if n == 0:
            return 0
    ✓ fact(4) = 24
        if n == 0:
            return 1
    4

    דוגמה פתורה: ספירה לאחור

    הפונקציה מדפיסה מספר, ואז קוראת לעצמה עם מספר קטן ב-1. כשמגיעים ל-0 היא מדפיסה Go! ועוצרת. עקבו אחרי המחסנית: היא גדלה עד הבסיס, ואז מתרוקנת.

    מחסנית הקריאות

      5

      תרגול

      6

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

      מה זה רקורסיה בפייתון?

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

      מה זה RecursionError: maximum recursion depth exceeded?

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

      רקורסיה או לולאה?

      כל רקורסיה אפשר לכתוב גם בלולאה. רקורסיה קצרה וברורה כשהבעיה בנויה מבעיות קטנות מאותו סוג, כמו עץ תיקיות או חיפוש בינארי. לולאה חוסכת זיכרון, כי אין מחסנית של קריאות. תרגלו למטה.