טכניקות אלגוריתמיותמתקדם8 דק' קריאה
תכנון דינמי - איך מפרקים בעיה לתת-בעיות?
הסבר על העיקרון מאחורי תכנון דינמי, ההבדל מ-memoization, וצעדים לפתרון בעיה.
תכנון דינמי (Dynamic Programming, DP) היא טכניקה לפתרון בעיות אופטימיזציה על ידי פירוק לתת-בעיות חופפות ושמירת התוצאות. זה ממיר אלגוריתמים אקספוננציאליים לפולינומיים - אבל דורש זיהוי נכון של המבנה.
שתי תכונות נדרשות
- Optimal Substructure - הפתרון האופטימלי בנוי מפתרונות אופטימליים של תת-בעיות
- Overlapping Subproblems - אותן תת-בעיות מופיעות שוב ושוב במהלך החישוב
דוגמה - סדרת פיבונאצ'י
// נאיבי - O(2^n)
function fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
// DP top-down (memoization) - O(n)
function fibMemo(n, memo = {}) {
if (n in memo) return memo[n];
if (n < 2) return n;
return memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
}
// DP bottom-up (tabulation) - O(n) זמן, O(1) זיכרון
function fibTab(n) {
let a = 0, b = 1;
for (let i = 2; i <= n; i++) [a, b] = [b, a + b];
return n === 0 ? 0 : b;
}Top-Down מול Bottom-Up
- Top-Down (Memoization) - רקורסיה + cache. אינטואיטיבי, מחשב רק תת-בעיות נחוצות
- Bottom-Up (Tabulation) - לולאה שממלאת טבלה מהקטן לגדול. נמנעת ממחסנית רקורסיה ולעיתים יעילה יותר בזיכרון
צעדים לפתרון בעיית DP
- זהו את התת-בעיה - מה משתנה בכל קריאה?
- הגדירו נוסחת רקורסיה (recurrence relation)
- זהו את מקרי הבסיס
- החליטו על שיטת מימוש (top-down/bottom-up)
- אופטימיזציה של זיכרון אם אפשר (לעיתים שומרים רק שורה אחרונה)
בעיות DP קלאסיות
- Knapsack (תרמיל גב) - בחירת פריטים עם משקל וערך
- LCS (Longest Common Subsequence) - תת-סדרה משותפת ארוכה
- Edit Distance - מרחק עריכה בין שתי מחרוזות
- Coin Change - מספר מינימלי של מטבעות לסכום נתון
אם נתקלים בבעיית אופטימיזציה רקורסיבית עם תת-בעיות חוזרות - זה כנראה DP. נסו קודם לחשוב על נוסחת הרקורסיה, ורק אז על המימוש.