Transcript Document

‫קורס תכנות‬
‫שיעור תשיעי‪ :‬רקורסיה‬
‫מודל הזיכרון של התכנית‬
‫• הקוד‬
‫• פקודות התכנית‬
‫קוד‬
‫קריאה‬
‫בלבד‬
‫• ‪Data‬‬
‫• מחרוזות שהוגדרו ע"י המתכנת‬
‫‪Data‬‬
‫• מחסנית (‪)Stack‬‬
‫• משמש לאיחסון משתנים לוקאליים‬
‫• )‪Last In First Out (LIFO‬‬
‫• בשליטת התכנית‬
‫מחסנית‬
‫קריאה‬
‫וכתיבה‬
‫• ערימה (‪)Heap‬‬
‫• בשליטת המתכנת (בהמשך הקורס)‬
‫ערימה‬
‫הרצת התכנית ‪ -‬שלבים‬
‫‪.1‬‬
‫‪.2‬‬
‫‪.3‬‬
‫‪.4‬‬
‫טעינת התכנית מהדיסק לזיכרון‬
‫• הקוד לחלק הקוד‪ ,‬המחרוזות הקבועות לחלק הקבוע‬
‫הקצאת ‪ Stack Frame‬עבור הפונקציה ‪main‬‬
‫• הקצאת מקום במחסנית עבור המשתנים המקומיים של ‪main‬‬
‫קריאה לפונקציה ‪main‬‬
‫ביצוע סדרתי של פקודות התכנית‬
‫• קריאה לפונקציה – הקצאה של ‪ Stack Frame‬עבור הפונקציה‬
‫וביצוע הקוד שלה‬
‫• חזרה מקריאה – ביטול ה ‪ Stack Frame‬וחזקה להמשך ביצוע‬
‫הפקודות ממקום הקריאה‬
strlen ‫קריאה ל‬
004147CC
‘H’
0
count
‘u’
‘m’
NULL
ptr
‘p’
‘t’
004147CC
humpty
004147D8
rhyme
main
‘y’
‘\0’
‘ ’
004147D8
‘H’
???
needlelen
004147D8
haystack
004147CC
needle
‘u’
’m’
.
.
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;
strstr
004147CC
strlen
‫רקע‪ :‬הגדרת נוסחאות‬
‫• דרך מקובלת להגדיר נוסחה או פעולה מתמטית היא‬
‫לרשום את שלבי החישוב שלה‪:‬‬
‫‪n! = 1*2*3*….*n‬‬
‫‪an = a*a*…..*a‬‬
‫‪ n‬פעמים‬
‫• מקובל גם לחשב את ערך הנוסחה שלב‪-‬אחר‪-‬שלב למשל‪:‬‬
‫‪4! = 1*2*3*4 = 2*3*4 = 6*4 = 24‬‬
‫דוגמא ‪ -‬עצרת‬
‫הגדרה איטרטיבית‬
‫‪n! = n * (n-1) * (n-2) * ...* 3 * 2 * 1‬‬
‫הגדרה רקורסיבית‬
‫!‪2! = 2 * 1‬‬
‫!‪3! = 3 * 2‬‬
‫!‪4! = 4 * 3‬‬
‫!)‪n! = n * (n-1‬‬
‫‪1! = 1‬‬
‫!‪5! = 5 * 4‬‬
‫דוגמא ‪ -‬חזקה‬
‫הגדרה איטרטיבית‬
‫‪an = a * a * a * ...* a * a * a‬‬
‫הגדרה רקורסיבית‬
‫‪22 = 2 * 21‬‬
‫‪23 = 2 * 22‬‬
‫‪24 = 2 * 23‬‬
‫‪an = a * an-1‬‬
‫‪a0 = 1‬‬
‫‪25 = 2 * 24‬‬
‫הגדרה רקורסיבית‬
‫‪ .1‬מקרה קצה פשוט (תנאי עצירה)‬
‫‪ .2‬כלל כיצד לצמצם את כל המקרים האחרים ב"כיוון" מקרה‬
‫הקצה‬
‫יתרונות‬
‫• קצר‬
‫• בהרבה מקרים‪ ,‬ההגדרה הרקורסיבית קצרה בהרבה‬
‫מהאיטרטיבית‬
‫• נוח‬
‫• במקרים מסוימים‪ ,‬ההגדרה הרקורסיבית היא ההגדרה הטבעית‬
‫והנוחה ביותר של מה שרוצים לחשב‬
‫דוגמא ‪ -‬סדרת פיבונאצ'י‬
‫• איברי הסדרה‬
‫‪1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ...‬‬
‫• שני האיברים הראשונים הם ‪ 1‬ו‪.1 -‬‬
‫• שאר האיברים מוגדרים כסכום שני האיברים שלפניהם‬
‫‪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‬‬
‫}‬
‫• פיבונאצ'י‬
‫‪int fibonaci(int n) /* n > 0 */‬‬
‫{‬
‫? )‪return (n == 1 || n == 2‬‬
‫‪1 :‬‬
‫;)‪fibonacci(n-1) + fibonacci(n-2‬‬
‫}‬
‫ איטרטיבי‬- ‫סדרת פיבונאצ'י‬
int fib(int n)
{
int i, next, fib1 = 1, fib2 = 1;
if (n == 1 ||
return 1;
n == 2)
for (i = 3; i <= n; i++)
{
next = fib1 + fib2;
fib1 = fib2;
fib2 = next;
}
return fib2;
}
‫דוגמת הרצה‬
int main()
{
printf("%d\n", factorial(3));
return 0;
{
main
3
3*2
factorial
2
2*1
factorial
1
1*1
factorial
0
1
factorial
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
main
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
fac
main
n
3
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
fac
n
2
fac
n
3
main
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
fac
n
1
fac
n
2
fac
n
3
main
‫בסיס הרקורסיה‬
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
1
facc
n
0
fac
n
1
fac
n
2
fac
n
3
main
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
1
fac
n
1
fac
n
2
fac
n
3
main
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
2
fac
n
2
fac
n
3
main
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
6
fac
main
n
3
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
main
int factorial(int n) /* n >= 0 */
{
return (n == 0) ?
1 :
n * factorial(n - 1);
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
printf
main
6
‫נקודות לתשומת‪-‬לב‪ :‬משתנים‬
‫• כשקוראים לפונקציה מתוך עצמה‪ ,‬משתנים שהוגדרו בפונקציה‬
‫הקוראת נשארים בזיכרון‪.‬‬
‫• כי המשתנים נשארים בזיכרון עד שהפונקציה מסתיימת‪.‬‬
‫• הרבה מאוד קריאות רקורסיביות עלולות למלא את הזיכרון של‬
‫המחשב‬
‫שימוש ברקורסיה‬
‫• מצד אחד‪:‬‬
‫• לפעמים לשימוש ברקורסיה יש יתרון ‪ -‬קל יותר לכתוב‬
‫באמצעותו את החישוב‬
‫• מצד שני‪:‬‬
‫• לא תמיד קל למצוא הגדרה רקורסיבית לפונקציה‬
‫• לא תמיד קל להבין תוכניות שנכתבו באופן רקורסיבי‬
‫• לכן נבחר להשתמש ברקורסיה במקרים שבאמת נוחים לכך‪.‬‬
‫רקורסיה – שימושים נוספים‬
‫‪ ‬לא רק לחישוב נוסחאות‬
‫‪ ‬אפשר להשתמש בה עבור כל בעיה שניתן לפתור על‪-‬ידי‬
‫פתרון של מקרה יותר קטן‪/‬פשוט שלה‬
‫‪ ‬סכום מערך (האיבר הראשון וסכום שאר המערך)‬
‫‪( strchr ‬התו הנוכחי או חיפוש בשאר המחרוזת)‬
‫‪... ‬‬
‫סכום מערך‬
int sum_array(int array[], int size)
{
if (size == 0)
return 0;
return array[0] + sum_array(array + 1, size - 1);
}
‫עוד דוגמאות‬
strchr •
char* strchr(const char *str, char c)
{
if (*str == '\0')
return NULL;
if (*str == c)
return str;
return strchr(str + 1, c);
}
strlen •
int strlen(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;
:‫תנאי עצירה‬
.0 ‫• מערך בגודל‬
.1 ‫• מערך בגודל‬
‫• המספר נמצא באמצע‬
‫נחפש בחצי השמאלי‬
if ( arr[ mid ] > num )
return binarySearch( arr, mid, num );
‫נחפש בחצי הימני‬
return binarySearch( arr+mid+1, size-mid-1, num );
}
‫‪Towers of Hanoi‬‬
‫• משימה‪:‬‬
‫• העבירו את כל הדיסקיות ממגדל ‪ A‬למגדל ‪C‬‬
‫‪C‬‬
‫‪B‬‬
‫‪A‬‬
‫‪Towers of Hanoi‬‬
‫• חוקים‪:‬‬
‫• מותר להזיז רק את הדיסקית העליונה‬
‫• אסור להניח דיסקית גדולה על דיסקית קטנה‬
‫‪C‬‬
‫‪B‬‬
‫‪A‬‬
‫‪Towers of Hanoi‬‬
‫•‬
‫הזזת מגדל בין ‪ n‬דיסקיות שקולה ל‪-‬‬
‫‪ .1‬הזזת מגדל בין ‪ n-1‬דיסקיות‬
‫‪C‬‬
‫‪B‬‬
‫‪A‬‬
‫‪Towers of Hanoi‬‬
‫•‬
‫הזזת מגדל בין ‪ n‬דיסקיות שקולה ל‪-‬‬
‫‪ .2‬הזזת דיסקית אחת‬
‫‪C‬‬
‫‪B‬‬
‫‪A‬‬
‫‪Towers of Hanoi‬‬
‫•‬
‫הזזת מגדל בין ‪ n‬דיסקיות שקולה ל‪-‬‬
‫‪ .3‬הזזת מגדל בין ‪ n-1‬דיסקיות‬
‫‪C‬‬
‫‪B‬‬
‫‪A‬‬
Towers of Hanoi - C
void move_disk(char from, char to)
{
printf("move disk from %c to %c\n", from, to);
}
void move_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);
}
}
‫רקורסיה ‪ -‬סיכום‬
‫‪ ‬פונקציה רקורסיבית היא פונקציה שקוראת לעצמה‬
‫‪ ‬מוגדרת בעזרת הפעלתה עבור פרמטרים יותר קטנים‪/‬פשוטים‪,‬‬
‫‪ ‬נדרש תנאי התחלה עבור פרמטר כלשהו‪ ,‬שמובטח שנגיע אליו‬
‫במהלך החישוב‪.‬‬
‫‪ ‬קל לתרגם נוסחה רקורסיבית לפונקציה רקורסיבית‬
‫‪ ‬לא תמיד קל למצוא ניסוח רקורסיבי אם הוא לא נתון‬