Smart-World Surf

יחידה 2: רקורסיה ומשפט האב

עקרונות הרקורסיה ושיטות לפתרון יחסי נסיגה.

ברוכים הבאים לשיעור הממוקד ביחידת "רקורסיה ומשפט האב" בקורס "מבני נתונים ומבוא לאלגוריתמים" (20407). יחידה זו היא אבן יסוד בהבנת אלגוריתמים וניתוח סיבוכיותם. נלמד כיצד אלגוריתמים רקורסיביים פועלים, איך לתאר את זמן הריצה שלהם באמצעות יחסי נסיגה, וכיצד לפתור יחסים אלו, בדגש על משפט האב – כלי עוצמתי ומרכזי לבחינה.

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

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

רקורסיה: תהליך שבו פונקציה קוראת לעצמה, במישרין או בעקיפין, כדי לפתור בעיה על ידי פירוקה לבעיות תת-קטנות יותר מאותו סוג.

מרכיבי פונקציה רקורסיבית

  • מקרה בסיס (Base Case): תנאי עצירה. זהו המקרה הפשוט ביותר של הבעיה, עבורו הפתרון ידוע וניתן להחזיר אותו ישירות ללא קריאות רקורסיביות נוספות. ללא מקרה בסיס, הרקורסיה תיכנס ללולאה אינסופית.
  • צעד רקורסיבי (Recursive Step): הבעיה המקורית מפורקת לבעיות תת-קטנות יותר מאותו סוג. הפונקציה קוראת לעצמה עם קלט קטן יותר, ומחברת את הפתרונות של בעיות התת-כדי לקבל את הפתרון לבעיה המקורית.

יתרונות הרקורסיה

קוד קומפקטי ואלגנטי, קריא וקל להבנה עבור בעיות מסוימות (לדוגמה, בעיות עץ או גרף). משקף לעיתים קרובות את ההגדרה המתמטית של הבעיה.

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

עלולה להיות פחות יעילה מאיטרציה (לולאות) עקב תקורה של קריאות לפונקציות (ניהול מחסנית). סיכון לגלישת מחסנית (Stack Overflow) עבור עומק רקורסיה גדול מדי.

ניתוח סיבוכיות של אלגוריתמים רקורסיביים: יחסי נסיגה

כדי לנתח את סיבוכיות הזמן של אלגוריתם רקורסיבי, אנו משתמשים ב"יחסי נסיגה" (Recurrence Relations). יחס נסיגה הוא משוואה או אי-שוויון המגדיר פונקציה באמצעות ערכיה בנקודות קטנות יותר.

יחס נסיגה: משוואה המגדירה את זמן הריצה של אלגוריתם רקורסיבי, בדרך כלל בצורה `T(n) = ...`, כאשר `T(n)` תלוי ב-`T(k)` עבור `k

בניית יחסי נסיגה

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

  • מקרה בסיס: העלות עבור הקלט הקטן ביותר (לרוב `T(1) = O(1)`).
  • מספר הקריאות הרקורסיביות: כמה פעמים הפונקציה קוראת לעצמה.
  • גודל הקלט בכל קריאה רקורסיבית: באיזה גודל קלט הפונקציה קוראת לעצמה.
  • עלות העבודה מחוץ לקריאות הרקורסיביות: כמה עבודה מתבצעת בכל צעד רקורסיבי (איחוד תוצאות, חלוקת קלט וכו').

דוגמאות:

  • חיפוש בינארי: `T(n) = T(n/2) + O(1)` (קריאה רקורסיבית אחת על חצי מהקלט, עבודה קבועה).
  • מיזוג ממוין (Merge Sort): `T(n) = 2T(n/2) + O(n)` (שתי קריאות רקורסיביות על חצי מהקלט כל אחת, עבודה לינארית במיזוג).

שיטות לפתרון יחסי נסיגה

שיטת ההצבה (Substitution Method)

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

שיטת עץ הרקורסיה (Recursion Tree Method)

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

משפט האב: כלי עוצמתי לפתרון מהיר

משפט האב (Master Theorem): מדוע הוא כה חשוב? משפט האב הוא כלי הכרחי לבחינה ולניתוח אלגוריתמים. הוא מאפשר לפתור במהירות וביעילות יחסי נסיגה נפוצים רבים, במיוחד אלו הנובעים מאלגוריתמי הפרד ומשול. הבנה מעמיקה של תנאיו ויישומיו חיונית להצלחה בקורס ובמבחן. שימו לב במיוחד למקרים השונים ולתנאים הספציפיים של כל מקרה!

משפט האב מספק פתרונות אסימפטוטיים (Big-O) ליחסי נסיגה מהצורה הכללית:

T(n) = aT(n/b) + f(n)

כאשר:

a: מספר הקריאות הרקורסיביות (a ≥ 1).
b: הגורם שבו גודל הקלט מתחלק בכל קריאה רקורסיבית (b > 1).
f(n): העלות של העבודה המתבצעת מחוץ לקריאות הרקורסיביות (חלוקה, איחוד, וכו').

שלושת המקרים של משפט האב

נגדיר את הערך הקריטי c_crit = log_b a. נשווה את f(n) ל-n^(c_crit).

  • מקרה 1: אם f(n) = O(n^(c_crit - ε)) עבור איזשהו ε > 0 (כלומר, f(n) קטן פולינומית מ-n^(c_crit)), אז:

    T(n) = Θ(n^(log_b a))

    העלות נשלטת על ידי העבודה בעלים של עץ הרקורסיה.

  • מקרה 2: אם f(n) = Θ(n^(c_crit)) (כלומר, f(n) שווה אסימפטוטית ל-n^(c_crit)), אז:

    T(n) = Θ(n^(log_b a) * log n)

    העלות מתחלקת באופן שווה בין כל רמות עץ הרקורסיה.

  • מקרה 3: אם f(n) = Ω(n^(c_crit + ε)) עבור איזשהו ε > 0 (כלומר, f(n) גדול פולינומית מ-n^(c_crit)), וגם מתקיים התנאי הרגולרי: a * f(n/b) ≤ c * f(n) עבור איזשהו קבוע c < 1 ו-n גדול מספיק, אז:

    T(n) = Θ(f(n))

    העלות נשלטת על ידי העבודה בשורש עץ הרקורסיה.

דגש חשוב למבחן: יש לזכור ולבדוק את התנאי הרגולרי במקרה 3. אם הוא לא מתקיים, משפט האב אינו ישים, ויש לחפש שיטות פתרון אחרות.

דוגמאות ויישומים נפוצים

מיזוג ממוין (Merge Sort)

יחס נסיגה: T(n) = 2T(n/2) + O(n)

כאן: a=2, b=2, f(n)=O(n). נחשב log_b a = log_2 2 = 1. לכן, n^(log_b a) = n^1 = n. f(n) = O(n), וזה מתאים למקרה 2 של משפט האב. פתרון: T(n) = Θ(n log n).

חיפוש בינארי (Binary Search)

יחס נסיגה: T(n) = T(n/2) + O(1)

כאן: a=1, b=2, f(n)=O(1). נחשב log_b a = log_2 1 = 0. לכן, n^(log_b a) = n^0 = 1. f(n) = O(1), וזה מתאים למקרה 2 של משפט האב. פתרון: T(n) = Θ(log n).

שאלות לדיון

  • 1. נתח את סיבוכיות הזמן של האלגוריתם הבא באמצעות משפט האב: T(n) = 3T(n/4) + n log n.
  • 2. נתח את סיבוכיות הזמן של האלגוריתם הבא: T(n) = 2T(n/2) + n / log n. האם משפט האב ישים? אם לא, מדוע?
  • 3. תאר אלגוריתם רקורסיבי לחישוב חזקה x^n בזמן O(log n). הצג את יחס הנסיגה המתאים ופתור אותו.

נקודות לתשובת מודל

  • 1. זיהוי: a=3, b=4, f(n) = n log n. חישוב log_b a = log_4 3 ≈ 0.792. נשווה f(n) ל-n^(0.792). מכיוון ש-n log n גדול פולינומית מ-n^(0.792) (לדוגמה, n log n = Ω(n^(0.792 + ε)) עבור ε = 0.1), נבדוק את מקרה 3. יש לבדוק את התנאי הרגולרי: 3 * (n/4) log(n/4) ≤ c * n log n. עבור c = 3/4 < 1, התנאי מתקיים עבור n גדול מספיק. לכן, T(n) = Θ(n log n).
  • 2. זיהוי: a=2, b=2, f(n) = n / log n. חישוב log_b a = log_2 2 = 1. נשווה f(n) ל-n^1 = n. הפונקציה f(n) = n / log n אינה קטנה פולינומית מ-n (מקרה 1), אינה שווה אסימפטוטית ל-n (מקרה 2), ואינה גדולה פולינומית מ-n (מקרה 3). הפער בין f(n) ל-n^(log_b a) אינו פולינומי. לכן, משפט האב אינו ישים. יש להשתמש בשיטות אחרות כגון עץ הרקורסיה.
  • 3. אלגוריתם רקורסיבי לחישוב x^n ב-O(log n):

    power(x, n):

    if n == 0: return 1

    if n % 2 == 0: return power(x * x, n / 2)

    else: return x * power(x * x, (n - 1) / 2)

    יחס נסיגה: T(n) = T(n/2) + O(1). (בכל שלב, אנו מבצעים קריאה רקורסיבית אחת על חצי מהקלט, בתוספת עבודה קבועה של כפל ובדיקת זוגיות/אי-זוגיות). פתרון באמצעות משפט האב (מקרה 2, כאשר log_2 1 = 0): T(n) = Θ(log n).

מצאתם טעות או שחסר משהו?
→ הקודמת
מבוא וניתוח סיבוכיות
הבאה ←
מבני נתונים בסיסיים