280 likes | 505 Vues
מבני נתונים. שיעור 2 - רקורסיה. מהי רקורסיה?. שיטה לפתרון בעיה ע"י חלוקת הבעיה לתתי בעיות הניתנות לפיתרון ע"י אותה השיטה...(??) במילים אחרות פונקציה שקוראת לעצמה... נשמע כמו מתכון בטוח ללולאה אינסופית?
E N D
מבני נתונים שיעור 2 - רקורסיה
מהי רקורסיה? • שיטה לפתרון בעיה ע"י חלוקת הבעיה לתתי בעיות הניתנות לפיתרון ע"י אותה השיטה...(??) • במילים אחרות פונקציה שקוראת לעצמה... נשמע כמו מתכון בטוח ללולאה אינסופית? • אלגוריתמים רקורסיביים מתוכננים כך שלא ימשיכו עד אינסוף ע"י תנאים וקריאה רקורסיבית עם פרמטרים שונים בכל פעם.
חישוב רקורסיבי של עצרת • חישוב עצרת: n!=1*2*3*…*n • פונקציה רקורסיבית המוגדרת כך: • שימו לב לשימוש בקריאה לאותה הפונקציה f, אך עם ערך שונה n-1 • במילים פשוטות: תגידו לי מה העצרת של n-1 ואני אדע לחשב את העצרת של n (ע"י מכפלה n*f(n-1))
חישוב רקורסיבי של עצרת • אם כדי לחשב את העצרת של n אני צריך לדעת את העצרת של n-1 ואז n-2 וכך הלאה, מתי התהליך יפסק? ואיך החישוב ייתבצע בפועל? • הקוד הבא הינו פונקציה (שיטה) רקורסיבית לחישוב עצרת: intrFactorial(int n){ if (n==1) return 1; //תנאי בסיס else return n*rFactorial(n-1);//קריאה רקורסיבית }
חלקי שיטה רקורסיבית • מקרה בסיס: ערכי קלט מסוימים שהפעלת השיטה עם ערכים אלו מחזירה ערך ללא קריאה רקורסיבית (זהו למעשה תנאי עצירה) • שיטה רקורסיבית חייבת להכיל לפחות מקרה בסיס אחד. • קריאה רקורסיבית: קריאה לאותה השיטה מתוכה. • ערכי הקלט שמועברים בקריאה הרקורסיבית חייבים להוביל לאחד ממקרי הבסיס.
return 4 * 6 = 24 final answer call recursiveFactorial ( 4 ) return 3 * 2 = 6 call recursiveFactorial ( 3 ) return 2 * 1 = 2 call recursiveFactorial ( 2 ) return 1 * 1 = 1 call recursiveFactorial ( 1 ) return 1 call recursiveFactorial ( 0 ) תרשים המחשה
Activation record n=3 n=2 long factorial (int n) { if (n<=1) return 1; else return n*factorial(n-1); } long factorial (int n) { if (n<=1) return 1; else return n*factorial(n-1); } n=1 long factorial (int n) { if (n<=1) return 1; else return n*factorial(n-1); }
Activation record 6 n=2 n=3 long factorial (int n) { if (n<=1) return 1; else return n*factorial(n-1); } long factorial (int n) { if (n<=1) return 1; else return n*factorial(n-1); } 2 2*1 3*2 1 n=1 long factorial (int n) { if (n<=1) return 1; else return n*factorial(n-1); }
פונקציה רקורסיבית • פונקציה רקורסיבית היא פונקציה שבתוך הגדרתה קוראת לעצמה. • פונקציה רקורסיבית מורכבת משני חלקים: • מקרה בסיסי )שהיא יודעת לפתור(. • מקרה מורכב שהיא יכולה לפרק לשני חלקים: • חלק אחד שהיא יודעת לפתור • חלק שני שהיא לא יודעת לפתור שהוא דומה לבעיה המקורית אבל פשוטה יותר. • תהליך פישוט הבעיה צריך להגיע למקרה בסיסי, אחרת הרקורסיה לא תיעצר. • הקושי – לפרק את הבעיה המורכבת לחלק שיודעים לפתור וחלק שהוא דומה לבעיה המקורית אבל פשוטה יותר!
רקורסיה - דגשים • פונקציה רקורסיבית היא פונקציה שבתוך הגדרתה קוראת לעצמה. • רקורסיה אינסופית, להבדיל מלולאה אינסופית מסתיימת בשגיאה. • בכל רקורסיה חייב להיות תנאי עצירה. • כל עותק חדש של הפונקציה מקבל עותק חדש של המשתנים פנימיים שלו.
רקורסיה לינארית • הצורה הכי פשוטה של רקורסיה היא רקורסיה לינארית. • ברקורסיה לינארית יש לכל היותר קריאה רקורסיבית אחת בכל פעם. • ייתכן ויהיו מספר אפשרויות לקריאות רקורסיביות, אך בכל הפעלה רק אחת מהאפשרויות תופעל. • כל האפשרויות לקריאות רקורסיביות חייבות להוביל למקרה בסיס.
הפיכת סדר של מערך Algorithm ReverseArray(A, i, j): Input: An array A and nonnegative integer indices iand j מדוע צריך? Output: The reversal of the elements in A starting at index iand ending at j if i < j then Swap A[i] and A[ j] ReverseArray(A, i+ 1, j - 1) return A = 1,2,3,4,5,6,7,8,9,10 ReverseArray(A,0,9) = 10,9,8,7,6,5,4,3,2,1
רקורסיה – מספרי פיבונאצ'י סידרת פיבונאצ'י: 0, 1, 1, 2, 3, 5, 8, 13, 21,…
חישובים חוזרים של תתי בעיות! רקורסיה – מספרי פיבונאצ'י סידרת פיבונאצ'י: long fibonacci(int n) { if (n == 1 || n == 0) return n; else return fibonacci(n-1) + fibonacci(n-2); }
סידרה של קריאות רקורסיביות f(3) f(2) f(1) return + return 1 return + f(1) f(0) return 1 return 0
זמן ריצה לאלגוריתם פיבונאצ'י • Let nk denote number of recursive calls made by fibonacci(k). Then • n0 = 1 • n1 = 1 • n2 = n1 + n0 + 1 = 1 + 1 + 1 = 3 • n3 = n2 + n1 + 1 = 3 + 1 + 1 = 5 • n4 = n3 + n2 + 1 = 5 + 3 + 1 = 9 • n5 = n4 + n3 + 1 = 9 + 5 + 1 = 15 • n6 = n5 + n4 + 1 = 15 + 9 + 1 = 25 • n7 = n6 + n5 + 1 = 25 + 15 + 1 = 41 • n8 = n7 + n6 + 1 = 41 + 25 + 1 = 67. • Note that nk > 2k/2. It is exponential!
דוגמא מחלק גדול ביותר- GCD • הגדרה מתמטית: המחלק הגדול ביותר (GCD) של שני מספרים שלמים הינו המספר השלם הגדול ביותר שמחלק את שניהם ללא שארית. • בעיה: בהינתן m,nשלמים כך ש- , מצא את GCD שלהם.
דוגמא מחלק גדול ביותר- GCD • הרעיון: אם m>n, אזי GCD(m,n) זהה ל- GCD(m-n,n). . • למה? אם m ו-n מתחלקים ב-d ללא שארית, אזי גם(m-n) מתחלקים ב-d ללא שארית. • לדוגמא: • m=80, n=24 GCD(80,24)=8 • 80=8*10, 24=8*3 80-24=8*(10-3)=8*7=56 • GCD(56,24)=8
דוגמא מחלק גדול ביותר- GCD • מקרה בסיסי? • פיתרון של מקרה כללי?
דוגמא מחלק גדול ביותר- GCD intgcd(int m, int n) { if(m == n) return m; else if (m > n) return gcd(m-n, n); else return gcd(m, n-m); } שימו לב כי אין פעולה על הערך המוחזר, פרט להעברתו. gcd(468, 24) gcd(444, 24) gcd(420, 24) ... gcd(36, 24) gcd(12, 24) (Now n is bigger) gcd(12, 12) (Same) 12
רקורסיה או לולאות • כל בעיה שניתנת לפיתרון ע"י רקורסיה גם ניתנת לפיתרון ע"י לולאות, והוא יותר יעיל. • אבל קיימות בעיות שבטבען רקורסיביות ופתרון איטרטיבי הוא מאוד מסובך.
ניתוח מקרה – מגדלי האנוי המטרה להעביר את כל הטבעות מעמוד A לעמוד C. ניתן להיעזר ב עמוד B. אסור להניח טבעת על טבעת קטנה ממנה! ע"פ אגדה הודית, נזירים עומלים יומם וליל על העברת 64 טבעות זהב וכשיסיימו העולם ייחרב... http://www.mazeworks.com/hanoi/index.htm
הרעיון: אם נניח שאנחנו יודעים איך להעביר n-1 טבעות, נוכל לפתור את הבעיה עבור n
אלגוריתם רקורסיבי לפתרון מגדלי האנוי • עבור n=1 העביר את הטבעת מ A ל C • העבר את n-1 הטבעות העליונות מ A ל B (העזר ב C) • העבר את הטבעת שנותרה (הכי גדולה) מ A ל C • העבר את n-1 הטבעות מ B ל C(העזר ב A)
אלגוריתם רקורסיבי – מגדלי האנוי public class TowerofHanoi { void moveDisk(int count, int start, int finish, int temp) { if(count > 0) { moveDisks(count-1, start, temp, finish); System.out.print("Moving a disk from "+start+"to"+ finish); moveDisks(count-1, temp, finish, start); } } // End of MoveDisks() method -------------------------------------------- public static void main(String[] args) { int DISKS = 6; moveDisks(DISKS, 1, 3, 2); } // End of main() method } // End of TowerofHanoi class
ניתוח סיבוכיות זמן הריצה • מספר הפעולות להזזת הדיסקיות: moves(n) = 1 if n = 1 moves(n) = 1 + 2 moves(n-1) if n <1 moves(n) = 1 + 2 moves (n-1) = 1 +2[1 + 2 moves (n-2)] = 1 +2[1 + 2 2[1 + 2 moves (n-3)]] = …………………………………… = 1 + 2 + 22 + 23 +………….+ 2n-1 = 20 + 21 + 22 + 23 +………….+ 2n-1 = 1(2n-1)/(2-1) = 2n-1 סכום סדרה הנדסית = O(2n)
האם סוף העולם מתקרב? • הנזירים ההודים צריכים להעביר 64 דיסקיות. • מצאנו שסיבוכיות הזמן של האלגוריתם הוא O(2n). • אם נניח שהנזירים הזריזים מעבירים דיסקית בשניה • כלומר עבור 64 דיסקיות (n=64) נקבל T(64) = 264שזה יוצא...18446744073709551616 שניות • נחלק ב 60*60*24*365 = 31536000 כדי לעבור לשנים ונקבל...58,494,417,355 שנים. • נניח שהנזירים עובדים כבר כמה מאות שנים, עדיין אין מקום לדאגה!!