360 likes | 538 Views
קורס תכנות. שיעור תשיעי: רקורסיה. מודל הזיכרון של התכנית. הקוד פקודות התכנית Data מחרוזות שהוגדרו ע"י המתכנת מחסנית ( Stack ) משמש לאיחסון משתנים לוקאליים Last In First Out (LIFO) בשליטת התכנית ערימה ( Heap ) בשליטת המתכנת (בהמשך הקורס). קוד. קריאה בלבד. Data. מחסנית. קריאה
E N D
קורס תכנות שיעור תשיעי: רקורסיה
מודל הזיכרון של התכנית • הקוד • פקודות התכנית • Data • מחרוזות שהוגדרו ע"י המתכנת • מחסנית (Stack) • משמש לאיחסון משתנים לוקאליים • Last In First Out (LIFO) • בשליטת התכנית • ערימה (Heap) • בשליטת המתכנת (בהמשך הקורס) קוד קריאה בלבד Data מחסנית קריאה וכתיבה ערימה
הרצת התכנית - שלבים • טעינת התכנית מהדיסק לזיכרון • הקוד לחלק הקוד, המחרוזות הקבועות לחלק הקבוע • הקצאת Stack Frame עבור הפונקציה main • הקצאת מקום במחסנית עבור המשתנים המקומיים של main • קריאה לפונקציה main • ביצוע סדרתי של פקודות התכנית • קריאה לפונקציה – הקצאה של Stack Frame עבור הפונקציה וביצוע הקוד שלה • חזרה מקריאה – ביטול ה Stack Frame וחזקה להמשך ביצוע הפקודות ממקום הקריאה
קריאה ל strlen main strstr if (*needle == '\0') return (char *) haystack; needlelen = strlen(needle); for ( ; (haystack = strchr(haystack, *needle)) != NULL; haystack++) if (strncmp(haystack, needle, needlelen) == 0) return (char *) haystack; return NULL; strlen
n פעמים רקע: הגדרת נוסחאות • דרך מקובלת להגדיר נוסחה או פעולה מתמטית היא לרשום את שלבי החישוב שלה: n! = 1*2*3*….*n an = a*a*…..*a • מקובל גם לחשב את ערך הנוסחה שלב-אחר-שלב למשל: 4! = 1*2*3*4 = 2*3*4 = 6*4 = 24
דוגמא - עצרת הגדרה איטרטיבית n! = n * (n-1) * (n-2) * ...* 3 * 2 * 1 n! = n * (n-1)! 1! = 1 הגדרה רקורסיבית 5! = 5 * 4! 4! = 4 * 3! 3! = 3 * 2! 2! = 2 * 1!
דוגמא - חזקה הגדרה איטרטיבית an = a * a * a * ...* a * a * a an = a * an-1 a0 = 1 הגדרה רקורסיבית 25 = 2 * 24 24 = 2 * 23 23 = 2 * 22 22 = 2 * 21
הגדרה רקורסיבית • מקרה קצה פשוט (תנאי עצירה) • כלל כיצד לצמצם את כל המקרים האחרים ב"כיוון" מקרה הקצה
יתרונות • קצר • בהרבה מקרים, ההגדרה הרקורסיבית קצרה בהרבה מהאיטרטיבית • נוח • במקרים מסוימים, ההגדרה הרקורסיבית היא ההגדרה הטבעית והנוחה ביותר של מה שרוצים לחשב
דוגמא - סדרת פיבונאצ'י • איברי הסדרה • שני האיברים הראשונים הם 1 ו- 1. • שאר האיברים מוגדרים כסכום שני האיברים שלפניהם 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ... Fib(1) = 1 Fib(2) = 1 Fin(n) = Fin(n-1) + Fib(n-2)
אז... • זה נורא מעניין, אבל איך זה קשור אלינו? • פונקציות ב C קוראות לפונקציות אחרות בפרט לעצמן! • עצרת • פיבונאצ'י • int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intfibonaci(int n) /* n > 0 */ { return (n == 1 || n == 2) ? 1 : fibonacci(n-1) + fibonacci(n-2); }
סדרת פיבונאצ'י - איטרטיבי int fib(int n) { inti, next, fib1 = 1, fib2 = 1; if (n == 1 || n == 2) return 1; for (i = 3; i <= n; i++) { next = fib1 + fib2; fib1 = fib2; fib2 = next; } return fib2; }
דוגמת הרצה int main() { printf("%d\n", factorial(3)); return0; { 3 2 1 0 main factorial factorial factorial factorial 3*2 2*1 1*1 1
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • {
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • {
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • {
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • {
בסיס הרקורסיה • int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • { 1
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • { 1
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • { 2
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • { 6
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • {
int factorial(int n) /* n >= 0 */ { return (n == 0) ? 1 : n * factorial(n - 1); } • intmain() • { • printf("%d\n", factorial(3)); • return0; • {
נקודות לתשומת-לב: משתנים • כשקוראים לפונקציה מתוך עצמה, משתנים שהוגדרו בפונקציה הקוראת נשארים בזיכרון. • כי המשתנים נשארים בזיכרון עד שהפונקציה מסתיימת. • הרבה מאוד קריאות רקורסיביות עלולות למלא את הזיכרון של המחשב
שימוש ברקורסיה • מצד אחד: • לפעמים לשימוש ברקורסיה יש יתרון - קל יותר לכתוב באמצעותו את החישוב • מצד שני: • לא תמיד קל למצוא הגדרה רקורסיבית לפונקציה • לא תמיד קל להבין תוכניות שנכתבו באופן רקורסיבי • לכן נבחר להשתמש ברקורסיה במקרים שבאמת נוחים לכך.
רקורסיה – שימושים נוספים • לא רק לחישוב נוסחאות • אפשר להשתמש בה עבור כל בעיה שניתן לפתור על-ידי פתרון של מקרה יותר קטן/פשוט שלה • סכום מערך (האיבר הראשון וסכום שאר המערך) • strchr (התו הנוכחי או חיפוש בשאר המחרוזת) • ...
סכום מערך intsum_array(int array[], int size) { if (size == 0) return 0; return array[0] + sum_array(array + 1, size - 1); }
עוד דוגמאות • strchr • strlen char* strchr(const char *str, char c) { if (*str == '\0') return NULL; if (*str == c) return str; return strchr(str + 1, c); } intstrlen(const char *str) { if (*str == '\0') return 0; return 1 + strlen(str + 1); {
חיפוש בינארי במערך ממויין int binarySearch(int *arr, int size, int num) { int mid = size/2; if ( size == 0 ) return 0; if ( size == 1 ) return (arr[0] == num); if ( arr[ mid ] == num ) return 1; if ( arr[ mid ] > num ) return binarySearch( arr, mid, num ); return binarySearch( arr+mid+1, size-mid-1, num ); } • תנאי עצירה: • מערך בגודל 0. • מערך בגודל 1. • המספר נמצא באמצע המערך. נחפש בחצי השמאלי נחפש בחצי הימני
Towers of Hanoi • משימה: • העבירו את כל הדיסקיות ממגדל A למגדל C A B C
Towers of Hanoi • חוקים: • מותר להזיז רק את הדיסקית העליונה • אסור להניח דיסקית גדולה על דיסקית קטנה A B C
Towers of Hanoi • הזזת מגדל בין nדיסקיות שקולה ל- • הזזת מגדל בין n-1דיסקיות A B C
Towers of Hanoi • הזזת מגדל בין nדיסקיות שקולה ל- • הזזת דיסקית אחת A B C
Towers of Hanoi • הזזת מגדל בין nדיסקיות שקולה ל- • הזזת מגדל בין n-1דיסקיות A B C
Towers of Hanoi - C voidmove_disk(char from, char to) { printf("move disk from %c to %c\n", from, to); } voidmove_tower(int height, char from, char to, char temp) { if (height == 1) { move_disk(from, to); } else { move_tower(height - 1, from, temp, to); move_disk(from, to); move_tower(height - 1, temp, to, from); } }
רקורסיה - סיכום • פונקציה רקורסיבית היא פונקציה שקוראת לעצמה • מוגדרת בעזרת הפעלתה עבור פרמטרים יותר קטנים/פשוטים, • נדרש תנאי התחלה עבור פרמטר כלשהו, שמובטח שנגיע אליו במהלך החישוב. • קל לתרגם נוסחה רקורסיבית לפונקציה רקורסיבית • לא תמיד קל למצוא ניסוח רקורסיבי אם הוא לא נתון