Transcript lesson4

‫קורס תכנות‬
‫שיעור רביעי‪ :‬פונקציות‪ ,‬מבוא‬
‫לרקורסיה‬
‫‪1‬‬
‫לולאות ‪ -‬תזכורת‬
‫• לולאה‪:‬‬
‫• קטע קוד המתבצע שוב ושוב ברצף כל עוד תנאי מסוים מתקיים‬
‫• לולאות ‪:for‬‬
‫• כאשר רוצים לבצע את הלולאה בצורה סדרתית‪:‬‬
‫• תבצע את הלולאה ‪ 10‬פעמים ‪...‬‬
‫• תבצע את הלולאה ‪ n‬פעמים ‪...‬‬
‫• לולאות ‪:while‬‬
‫• כאשר רוצים לבצע את הלולאה מספר שרירותי של פעמים‪:‬‬
‫• כל עוד ‪ i‬זוגי‪...‬‬
‫• כל עוד לא הייתה טעות בחישוב ‪...‬‬
‫‪2‬‬
‫לולאות ‪ -‬תזכורת‬
‫• ‪:break‬‬
‫• מסיים (שובר) את ריצת הלולאה‬
‫• ‪:continue‬‬
‫• מסיים רק את הסיבוב (‪ )iteration‬הנוכחי של הלולאה‬
‫‪3‬‬
‫לולאות ‪do-while‬‬
‫ההבדל בין ‪ do-while‬ל‪:while-‬‬
‫• הפקודות מבוצעות לפחות פעם אחת‬
‫(אפילו אם התנאי לא מתקיים לעולם)‪.‬‬
‫• התנאי נבדק לאחר ביצוע הפקודות‪.‬‬
‫‪4‬‬
‫‪initialize ...‬‬
‫‪do‬‬
‫{‬
‫‪do something ...‬‬
‫‪increment ...‬‬
‫}‬
‫;) ‪while ( condition‬‬
?‫כיצד מחשבים‬
#include <stdio.h>
int main()
{
int i = 0;
double sum = 0, result;
for (result = 1, i = 1; i <= 20; ++i)
result = result * 2;
sum += result;
:‫כיצד מחשבים את‬
220 + 315 + 517
220 ‫חישוב‬
for(result = 1, i = 1; i <= 15; ++i)
result = result * 3;
sum += result;
315 ‫חישוב‬
for(result = 1, i = 1; i <= 17; ++i)
result = result * 5;
sum += result;
517 ‫חישוב‬
printf("2^20 + 3^15 + 5^17= %g", sum);
return 0;
}
5
‫מה היינו רוצים?‬
‫• בעיות‪:‬‬
‫• שכפול של קטע קוד כמעט זהה ‪ 3‬פעמים‬
‫• אם היו יותר מחוברים היה צריך לשכפל פעמים נוספות‬
‫• התכנית מתארכת ונהיית מסורבלת‬
‫• מקור לבאגים וטעויות ‪copy / paste‬‬
‫• קשה להבין "למה התכוון המשורר"‬
‫• מה היינו רוצים?‬
‫• לכתוב פעם אחת בלבד את הקוד‬
‫• להשתמש באותו קטע קוד מספר פעמים אבל עם פרמטרים שונים‬
‫בכל פעם‬
‫‪6‬‬
‫ פונקציות‬- ‫פתרון‬
#include <stdio.h>
double power(double base, int exponent)
{
int i = 0;
power ‫הגדרת פונקציה בשם‬
double result = 1;
exponent
base
‫המחשבת את‬
for (i = 1; i <= exponent; i++)
result = result * base;
return result;
}
‫התוכנית מתחילה לרוץ מכאן‬
int main()
{
double sum = power(2,20) + power(3,15) + power(5,17);
printf("2^20 + 3^15 + 5^17= %g", sum);
return 0;
}
‫קריאה‬
‫לפונקציה‬
7
‫פונקציות‬
‫• פונקציה‪:‬‬
‫• קטע קוד בעל שם‬
‫• יכול לקבל ערכי קלט לתוך משתנים‬
‫• יכול להחזיר ערך פלט יחיד‬
‫• שימושים‪:‬‬
‫• אפשר לקרוא לפונקציה הרבה פעמים במהלך התוכנית‪ ,‬קריאה עם‬
‫קלט שונה יכולה להניב תוצאה שונה‬
‫• מיחזור קוד‪:‬‬
‫• כותבים פעם אחת‪ ,‬בודקים פעם אחת ‪ ...‬משתמשים הרבה פעמים‬
‫• לדוגמא‪printf, scanf :‬‬
‫• בהירות קוד‪ :‬פיתוח וקריאה (אבסטרקציה)‬
‫• עקרון ההכמסה (‪)encapsulation‬‬
‫‪8‬‬
‫ושוב ‪ ...‬למה להשתמש בפונקציות?‬
‫• חלוקת התכנית לחלקים לוגים שונים‬
‫• כל חלק מבצע פעולה בסיסית אחרת‬
‫• קריאת נתונים‪ ,‬עיבוד נתונים‪ ,‬הדפסה ‪...‬‬
‫• פירוק בעיה מסובכת לתת בעיות פשוטות יותר ‪ -‬אבסטרקציה‬
‫• שימוש חוזר באותו קטע קוד מספר פעמים באותה התוכנית‬
‫• שימוש חוזר באותו קטע קוד בתכניות שונות‬
‫• ניתן להשתמש בפונקציות שכתבנו גם בתוכניות אחרות‬
‫• לדוגמא ‪ printf‬שמופיעה בספרייה ‪.stdio.h‬‬
‫‪9‬‬
‫ דוגמא‬- ‫משתנים בפונקציה‬
#include <stdio.h>
double power(double base, int exponent)
{
int i = 0;
:‫המשתנים‬
double result = 1;
i, result, base, exponent
for (i = 1; i <= exponent; ++i)
result = result * base;
power ‫מוגדרים אך ורק בפונקציה‬
return result;
}
sum ‫המשתנה‬
main -‫מוגדר אך ורק ב‬
int main()
{
double sum = power(2,20) + power(3,15) + power(5,17);
printf(“2^20 + 3^15 + 5^17= %g”, sum);
return 0;
}
10
‫ דוגמא‬- ‫משתנים בפונקציה‬
#include <stdio.h>
double power(double base, int exponent)
{
int i = 0;
double result = 1;
‫אפשר להגדיר בפונקציות שונות משתנים‬
‫שונים באותו השם‬
)result ‫(למשל‬
for (i = 1; i <= exponent; ++i)
result = result * base;
return result;
}
!‫אין כל קשר בין שני המשתנים‬
int main()
{
double result = power(2,20) + power(3,15) + power(5,17);
printf(“2^20 + 3^15 + 5^17= %g”, result);
return 0;
}
11
‫פונקציות – העברת ערכים‬
‫>‪#include <stdio.h‬‬
‫אל פונקציה מועברים ערכים (השמה)‬
‫ולא המיקום בזיכרון‪ ,‬למעשה יש כאן‬
‫העתקה של הערך במשתנה‬
‫שינוי ערך המשתנה בפונקציה לא משפיע על‬
‫המשתנה המקורי‬
‫)‪int square(int num‬‬
‫{‬
‫;‪num = num * num‬‬
‫;‪return num‬‬
‫}‬
‫)(‪int main‬‬
‫{‬
‫;‪int num = 16‬‬
‫;))‪printf(“The square of %d is %d”, num, square(num‬‬
‫;‪return 0‬‬
‫}‬
‫הפלט ‪:‬‬
‫‪12‬‬
‫‪The square of 16 is 256‬‬
‫בזמן ריצה‬
source code
double power(double base, int exponent)
{
int i = 0;
double result = 1;
call stack
main
for (i = 1; i <= exponent; i++)
result = result * base;
base
return result;
}
power
int main()
{
double result = power(2,20) +
power(3,15) +
power(5,17);
result
exponent
=5
3
2
= 17
15
20
i
result
printf("2^20 + 3^15 + 5^17= %g",
result);
return 0;
}
13
‫הכרזה על פונקציות‬
‫• על מנת להשתמש בפונקציה‪ ,‬יש להגדירה‬
‫• לכן הפונקציה מומשה לפני ה‪main-‬‬
‫• אופציה נוספת היא‪:‬‬
‫• להכריז על הפונקציה בתחילת הקובץ (ללא מימוש)‬
‫• ‪Function Declaration‬‬
‫• לממש את הפונקציה בהמשך הקובץ‪ ,‬או בקובץ נפרד‬
‫• ‪Implementation‬‬
‫• ההכרזה על הפונקציה (‪ )prototype‬והמימוש חייבים להיות זהים!‬
‫‪14‬‬
‫דוגמה להכרזה על פונקציות‬
double power(double base, int exponent);
...
...
implement something
...
...
implement something else
...
...
double power(double base, int exponent)
{
int i;
double result = 1;
for (i = 1; i <= exponent; i++)
result = result * base;
return result;
}
‫הכרזה על הפונקציה‬
)function declaration(
‫מימוש הפונקציה‬
)function implementation(
15
‫בשביל מה צריך הכרזה על פונקציות?‬
‫• לפעמים נוח לראות בתחילת הקובץ אילו פונקציות ממומשות בקובץ‬
‫• אם התכנית מורכבת ממספר קבצים‪,‬‬
‫• ההגדרה של הפונקציות צריכה להופיע רק באחד מהקבצים‬
‫• בכל שאר הקבצים תופיע רק ההכרזה‬
‫• למשל‪ ,‬שימוש בספריות של פונקציות‪ ,‬כמו ‪stdio.h‬‬
‫‪16‬‬
‫הכרזה על פונקציות – ספריות‬
‫• >‪#include <filename‬‬
‫• הקומפיילר מצרף לקובץ שלנו את הקובץ ‪filename‬‬
‫• כאשר ‪ filename‬הוא אחד מקבצי ההכרזות של ‪C‬‬
‫• הקובץ מכיל הכרזות של פונקציות שימושיות‬
‫• לדוגמא ‪ stdio.h‬מכיל פונקציות קלט ‪ /‬פלט (כמו ‪)printf,scanf‬‬
‫• כך אנחנו יכולים להשתמש בפונקציות מבלי להכיר את המימוש שלהן‬
‫• בזמן הקומפילציה ה‪ linker -‬מצרף את המימוש שלהן (מתוך הספריות‬
‫הקיימות של ‪ )C‬לתוכנית שלנו‬
‫‪17‬‬
‫ספריות נוספות‬
‫• ב‪ C -‬קיימות ספריות נוספות לשימושינו‬
‫• ב‪ math.h -‬נוכל למצוא פונקציות כמו‪:‬‬
‫• ‪sin‬‬
‫• ‪cos‬‬
‫• ‪abs‬‬
‫• ‪log‬‬
‫• ‪sqrt‬‬
‫• ‪...‬‬
‫• ב‪ ctype.h -‬נוכל למצוא פונקציות שעוסקות בתווים כמו‪:‬‬
‫• ‪toupper‬‬
‫• ‪Islower‬‬
‫• ‪...‬‬
‫‪18‬‬
‫סיכום ביניים‬
‫•‬
‫•‬
‫•‬
‫•‬
‫‪19‬‬
‫מהן פונקציות ולמה הן שימושיות‬
‫קריאה לפונקציה והערך שמוחזר ממנה‬
‫משתנים בפונקציות והעברת ערכים אליהן‬
‫הכרזה על פונקציות וספריות של פונקציות‬
‫פונקציה יכולה לקרוא גם לעצמה‬
?‫• מה תעשה התוכנית הבאה‬
void printNum(int num)
{
printf("%d ", num);
printNum(num-1);
{
int main()
{
printNum(3);
return 0;
{
...‫התוכנית "תעוף" כשיגמר הזכרון‬
main
printNum
num=3
printNum
num=2
printNum
num=1
20
‫תנאי עצירה‬
?‫• מה תעשה התוכנית הבאה‬
void printNum(int num)
{
if (num == 0)
return;
printf("%d ", num);
printNum(num-1);
{
int main()
{
printNum(3);
return 0;
{
main
printNum
num=3
printNum
num=2
printNum
num=1
printNum
num=0
21
‫חשוב לקדם משתנה מקריאה לקריאה‬
?‫• מה תעשה התוכנית הבאה‬
void printNum(int num)
{
if (num == 0)
return;
printf("%d ", num);
printNum(num);
{
main
printNum
num=3
printNum
num=3
int main()
printNum
{
printNum(3);
num=3
return 0;
{
...‫התוכנית "תעוף" כשיגמר הזכרון‬
22
‫דוגמא נוספת‬
?‫• מה תעשה התוכנית הבאה‬
void printNum(int num)
{
if (num == 0)
return;
printf("%d ", num);
printNum(num-1);
printf("%d ", num);
{
int main(){
printNum(3);
return 0;
{
main
printNum
num=3
printNum
num=2
printNum
num=1
printNum
num=0
23
‫רקורסיה‬
‫• פונקציה שקוראת לעצמה נקראת פונקציה רקורסיבית‪.‬‬
‫‪24‬‬
‫מגדלי הנוי‬
‫• משימה‪:‬‬
‫• העבירו את כל הדיסקיות מיתד ‪ )source( S‬ליתד ‪)target( T‬‬
‫• השתמשו ביתד עזר ‪)auxiliary( A‬‬
‫‪25‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫החוקים‬
‫• מותר להעביר רק את הדיסקית העליונה‬
‫• אסור להניח דיסקית גדולה על דיסקית קטנה‬
‫‪26‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫עבור דסקית אחת – קל מאוד!‬
‫•‬
‫‪27‬‬
‫דסקית תכלת מ‪ S-‬ל‪T-‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫עבור ‪ 2‬דסקיות – קל מאוד!‬
‫•‬
‫•‬
‫•‬
‫‪28‬‬
‫דסקית כחולה מ‪ S-‬ל‪A-‬‬
‫דסקית תכלת מ‪ S-‬ל‪T-‬‬
‫דסקית כחולה מ‪ A-‬ל‪T-‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫עבור ‪ 3‬דסקיות – קל‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫‪29‬‬
‫דסקית ירוקה מ‪ S-‬ל‪T-‬‬
‫דסקית כחולה מ‪ S-‬ל‪A-‬‬
‫דסקית ירוקה מ‪ T-‬ל‪A-‬‬
‫דסקית תכלת מ‪ S-‬ל‪T-‬‬
‫דסקית ירוקה מ‪ A-‬ל‪S-‬‬
‫דסקית כחולה מ‪ A-‬ל‪T-‬‬
‫דסקית ירוקה מ‪ S-‬ל‪T-‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫עבור ‪ 4‬דסקיות?‬
‫• ראינו שיש פתרון עבור ‪ 3‬דסקיות‬
‫• אפשר להשתמש בו על מנת לפתור עבור ‪!4‬‬
‫‪30‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫פתרון עבור ‪ 4‬דסקיות‬
‫‪31‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫עבור ‪ n‬דסקיות?‬
‫‪32‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫נניח שיש לי פתרון עבור ‪ n-1‬דסקיות‬
‫‪33‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫העברת ‪ n‬דיסקיות מ‪ S-‬ל‪T-‬‬
‫‪ .1‬העברת ‪ n-1‬הדיסקיות הקטנות מ‪ S-‬ל‪ ,A-‬בעזרת יתד‬
‫עזר ‪T‬‬
‫‪34‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫העברת ‪ n‬דיסקיות מ‪ S-‬ל‪( T-‬המשך)‬
‫‪ .2‬העברת הדיסקית שנותרה מ‪ S-‬ל‪T-‬‬
‫‪35‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫העברת ‪ n‬דיסקיות מ‪ S-‬ל‪( T-‬המשך)‬
‫‪ .3‬העברת ‪ n-1‬הדיסקיות העליונות מ‪ A-‬ל‪ ,T-‬בעזרת יתד‬
‫עזר ‪S‬‬
‫‪36‬‬
‫‪T‬‬
‫‪A‬‬
‫‪S‬‬
‫ומה עם פתרון עבור ‪ n-1‬דסקיות?‬
‫!‪Easy‬‬
‫‪37‬‬
‫אלגוריתם – עבור ‪ n‬דסקיות‬
‫‪T‬‬
‫‪.1‬‬
‫‪.2‬‬
‫‪.3‬‬
‫‪.4‬‬
‫‪38‬‬
‫‪A‬‬
‫‪S‬‬
‫אם צריך להעביר דסקית אחת – קל!‬
‫העבר ‪ n-1‬דסקיות מ‪ S-‬ל‪( A-‬יתד עזר ‪)T‬‬
‫העבר דסקית מ‪ S-‬ל‪T-‬‬
‫העבר ‪ n-1‬דסקיות מ‪ A-‬ל‪( T-‬יתד עזר ‪)S‬‬
‫נתרגם זאת לשפת ‪...C‬‬
‫ לפתירת מגדלי הנוי‬C ‫תוכנית‬
void hanoi(char diskNum, char s, char t, char a);
int main(){
hanoi(3,'S','T','A');
return 0;
{
!‫אם צריך להעביר דסקית אחת – קל‬.1
)T ‫ (יתד עזר‬A-‫ ל‬S-‫ דסקיות מ‬n-1 ‫העבר‬.2
T-‫ ל‬S-‫העבר דסקית מ‬.3
)S ‫ (יתד עזר‬T-‫ ל‬A-‫ דסקיות מ‬n-1 ‫העבר‬.4
void hanoi(char diskNum, char s, char t, char a){
if (diskNum == 1){
printf("Move disk from %c to %c\n", s, t);
return;
{
hanoi(diskNum-1,s,a,t);
printf("Move disk from %c to %c\n", s, t);
hanoi(diskNum-1,a,t,s);
{
?‫זוכרים את מחסנית הקריאות‬
main
hanoi(3,'S','T','A')
hanoi(2,'S',‘A',‘T')
hanoi(1,'S',‘T',‘A')
40
?‫זוכרים את מחסנית הקריאות‬
main
hanoi(3,'S','T','A')
hanoi(2,'S',‘A',‘T')
hanoi(1,‘T',‘A',‘S')
...‫וכן הלאה‬
41
‫רקורסיה‬
‫• פתרנו את בעיית מגדלי הנוי בעזרת רקורסיה‬
‫• כלומר בעזרת פונקציה שקוראת לעצמה‪.‬‬
‫• רקורסיה מאפשרת לנו לפתור בעיה "גדולה" בעזרת‬
‫פתרון של בעיות "קטנות" המרכיבות אותה‪.‬‬
‫• בכל קריאה רקורסיבית אנחנו "מקטינים" את הבעיה‬
‫ולבסוף מגיעים למקרה קצה שאותו קל לפתור באופן‬
‫ישיר‪.‬‬
‫דוגמא‪ :‬הגדרת נוסחאות‬
‫• דרך מקובלת להגדיר נוסחה או פעולה מתמטית היא‬
‫לרשום את שלבי החישוב שלה‪:‬‬
‫‪n! = 1*2*3*….*n‬‬
‫‪an = a*a*…..*a‬‬
‫‪ n‬פעמים‬
‫• אפשר לחשב את ערך הנוסחה שלב‪-‬אחר‪-‬שלב‬
‫(איטרטיבית) למשל‪:‬‬
‫‪4! = 1*2*3*4 = 2*3*4 = 6*4 = 24‬‬
‫הגדרת נוסחאות‬
‫• דרך נוספת להגדיר נוסחאות – הגדרה רקורסיבית‬
‫מגדירים את הערך של השלב האחרון בעזרת תוצאות השלבים שלפניו‪.‬‬
‫• במקום להגדיר עצרת על‪-‬ידי‪:‬‬
‫‪n!=1*2*3*….*n‬‬
‫• אפשר להגדיר על‪-‬ידי‪:‬‬
‫!)‪n!=n*(n-1‬‬
‫‪0!=1‬‬
‫ואז החישוב יהיה‪:‬‬
‫= ))!‪4! = 4*3! = 4*(3*2!) = 4*(3*(2*1‬‬
‫= )‪4*(3*(2*(1*0!))) = 4*(3*(2*1)) = 4*(3*2‬‬
‫‪4*6 = 24‬‬
‫הגדרה רקורסיבית‬
‫•‬
‫•‬
‫מורכבת משני חלקים‪:‬‬
‫‪ .1‬פירוט של שלב אחד בנוסחה‬
‫‪ .2‬ציון תוצאה עבור ערך התחלתי כלשהו ("בסיס הרקורסיה")‬
‫(אחרת חישוב הנוסחה לא מסתיים)‬
‫דוגמא נוספת‪:‬‬
‫הגדרה איטרטיבית‪:‬‬
‫‪an = a*a*…..*a‬‬
‫הגדרה רקורסיבית‪:‬‬
‫‪an = a*an-1‬‬
‫‪a0 = 1‬‬
‫ואז‪:‬‬
‫= ))‪43 = 4*42 = 4*(4*41) = 4*(4*(4*40‬‬
‫‪= 4*(4*(4*1)) = 4*(4*4) = 4*16 = 64‬‬
‫יתרונות‬
‫• קצר‬
‫• בהרבה מקרים‪ ,‬ההגדרה הרקורסיבית קצרה בהרבה‬
‫מהאיטרטיבית‬
‫• נוח‬
‫• במקרים מסוימים‪ ,‬ההגדרה הרקורסיבית היא ההגדרה הטבעית‬
‫והנוחה ביותר של מה שרוצים לחשב‬
‫דוגמא ‪ -‬סדרת פיבונאצ'י‬
‫• איברי הסדרה‬
‫‪0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ...‬‬
‫• שני האיברים הראשונים הם ‪ 0‬ו‪1-‬‬
‫• שאר האיברים מוגדרים כסכום שני האיברים שלפניהם‬
‫‪Fib(1) = 0‬‬
‫‪Fib(2) = 1‬‬
‫)‪Fin(n) = Fin(n-1) + Fib(n-2‬‬
‫• פחות נוח לרשום במקרה הזה הגדרה איטרטיבית (נוסחה מפורשת)‪.‬‬
‫• החישוב עפ"י הנוסחא הרקורסיבית הוא‪:‬‬
‫))‪Fib(4) = Fib(3)+Fib(2) = (Fib(2)+Fib(1))+Fib(2) = (1 + Fib(1‬‬
‫‪+ Fib(2) = … = 2‬‬
C ‫חישוב נוסחאות רקורסיביות בשפת‬
n! = n*(n-1)!
0! = 1
int factorial (int n) /* n >= 0 */
{
if (n == 0)
return 1;
return (n * factorial(n-1));
}
‫• עצרת‬
‫דוגמת הרצה‬
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 */
{
if (n==0)
return 1;
return (n * factorial(n-1));
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
main
‫דוגמת הרצה‬
int factorial(int n) /* n >= 0 */
{
if (n==0)
return 1;
return (n * factorial(n-1));
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
fac
main
n
3
‫דוגמת הרצה‬
int factorial(int n) /* n >= 0 */
{
if (n==0)
return 1;
return (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 */
{
if (n==0)
return 1;
return (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 */
{
if (n==0)
return 1;
return (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 */
{
if (n==0)
return 1;
return (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 */
{
if (n==0)
return 1;
return (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 */
{
if (n==0)
return 1;
return (n * factorial(n-1));
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
6
fac
main
n
3
int factorial(int n) /* n >= 0 */
{
if (n==0)
return 1;
return (n * factorial(n-1));
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
main
int factorial(int n) /* n >= 0 */
{
if (n==0)
return 1;
return (n * factorial(n-1));
}
int main()
{
printf("%d\n", factorial(3));
return 0;
{
printf
main
6
‫נקודות לתשומת‪-‬לב‬
‫‪int factorial (int n) /* n >= 0 */‬‬
‫{‬
‫ללא תנאי העצירה (בסיס הרקורסיה)‪,‬‬
‫)‪if (n==0‬‬
‫החישוב לא יסתיים לעולם‬
‫;‪return 1‬‬
‫;))‪return (n * factorial(n-1‬‬
‫}‬
‫בכל פונקציה רקורסיבית חייב להיות תנאי עצירה‪:‬‬
‫מקרה בו הפונקציה תחזיר ערך מבלי לקרוא לעצמה שוב‬
‫נקודות לתשומת‪-‬לב‬
‫‪int factorial (int n) /* n >= 0 */‬‬
‫{‬
‫)‪if (n==0‬‬
‫;‪return 1‬‬
‫;))‪return (n * factorial(n-1‬‬
‫}‬
‫התקדמות לעבר תנאי העצירה‬
‫כדי שהתוכנית תסתיים‪ ,‬צריך לדאוג לכך שבאיזשהו‬
‫שלב תנאי העצירה יתקיים (בדיוק כמו בלולאות)‬
‫נקודות לתשומת‪-‬לב‪ :‬זכרון‬
‫• כשקוראים לפונקציה מתוך עצמה‪ ,‬משתנים שהוגדרו בפונקציה‬
‫הקוראת נשארים בזיכרון (בסביבה במחסנית)‪.‬‬
‫‪ ‬משום שהמשתנים נשארים בזיכרון עד שהפונקציה מסתיימת‪.‬‬
‫• הרבה מאוד קריאות רקורסיביות עלולות למלא את הזיכרון של‬
‫המחשב (דוגמא בהמשך)‬
‫• גם אם נחסוך במשתנים‪ ,‬נדרש מקום בכל קריאה לפונקציה‪:‬‬
‫‪ ‬עבור הנקודה שאליה היא צריכה לחזור‬
‫‪ ‬עבור הערך שהיא צריכה להחזיר‬
‫איך נחשב חזקה באופן רקורסיבי?‬
‫• ההגדרה הרקורסיבית (למעריך שלם ואי‪-‬שלילי)‪:‬‬
‫‪an = a.an-1, a0=1‬‬
‫• והפונקציה ב‪:C -‬‬
‫)‪double power(double base, int exp‬‬
‫{‬
‫;‪if (exp==0) return 1‬‬
‫;)‪return base * power(base, exp-1‬‬
‫}‬
‫אפשר לטפל גם במעריך שלילי‬
‫איך נחשב חזקה באופן רקורסיבי‬
:‫ ההגדרה היא‬,‫• אם יתכן מעריך שלילי‬
an = 1/a-n
:n>0 ‫עבור‬
an = a . an-1,
a0=1
:n≥0 ‫עבור‬
:C -‫• והפונקציה ב‬
double power(double base, int exp)
{
if (exp < 0) return 1/power(base, -exp);
if (exp==0) return 1;
return base * power(base, exp-1);
}
‫סדרת פיבונאצ'י‬
‫• נוסחה שמוגדרת באופן רקורסיבי‪:‬‬
‫)‪fib(n)=fib(n-1)+fib(n-2‬‬
‫‪fib(1)=0, fib(2)=1‬‬
‫• נוכל לכתוב את זה כך ב‪:C -‬‬
‫)‪int fib (int n‬‬
‫{‬
‫))‪if ((n==1) || (n==2‬‬
‫;‪return n-1‬‬
‫;)‪return fib(n-1)+fib(n-2‬‬
‫}‬
‫מה ההבדל בין הדוגמא הזאת‬
‫לשתי הדוגמאות הקודמות שראינו?‬
‫חישוב סדרת פיבונאצ'י‬
‫• כל קריאה לפונקציה עם ‪ ,n>2‬יוצרת שתי קריאות נוספות לפונקציה‬
‫• אם גם בהן ‪ ,n>2‬כל אחת מהן יוצרת שתי קריאות נוספות‬
‫• וכן הלאה‪.‬‬
‫• כבר עבור ‪ n‬קטן יחסית (למשל ‪:)50‬‬
‫‪ ‬נקבל מספר עצום של קריאות לפונקציה‬
‫)‪int fib (int n‬‬
‫{‬
‫))‪if ((n==1) || (n==2‬‬
‫;‪return n-1‬‬
‫;)‪return fib(n-1)+fib(n-2‬‬
‫}‬
‫קריאות לפונקציה ‪fib‬‬
‫מספר קריאות‬
‫ערך‬
‫‪n‬‬
‫‪1‬‬
‫‪1‬‬
‫‪1‬‬
‫‪1‬‬
‫‪1‬‬
‫‪2‬‬
‫‪3‬‬
‫‪2‬‬
‫‪3‬‬
‫‪92735‬‬
‫‪28657‬‬
‫‪23‬‬
‫‪150049‬‬
‫‪46368‬‬
‫‪24‬‬
fib ‫קריאות לפונקציה‬
Fib(6)
Fib(4)
Fib(5)
Fib(4)
Fib(3)
Fib(3)
Fib(2)
Fib(2)
Fib(1)
Fib(2)
Fib(3)
Fib(1)
Fib(2)
Fib(2)
Fib(1)
‫סדרת פיבונאצ'י – בעיית יעילות‬
‫• לחישוב האיבר ה‪ 24-‬בסדרה צריך לחשב את ‪ 23‬הערכים שלפניו‬
‫‪ ‬אבל בחישוב הרקורסיבי יתבצעו עשרות אלפי קריאות לפונקציה‬
‫‪ ‬כלומר יחושבו עשרות אלפי ערכים‪.‬‬
‫• הסיבה היא ששני הערכים שמחושבים בקריאה הרקורסיבית לא נשמרים‬
‫‪ ‬לכן ברקורסיה יבוצעו הרבה פעמים את הקריאות )‪ ,fib(1), fib(2‬וכו'‪.‬‬
‫• מסקנה‪:‬‬
‫‪ ‬אם פונקציה רקורסיבית צריכה לקרוא לעצמה יותר מפעם אחת‪ ,‬אז‬
‫חישובה הוא בעייתי‪ ,‬ויתאפשר רק למספרים קטנים‪.‬‬
‫‪ ‬עבור מספרים גדולים נצטרך לחשב ולהגדיר איטרטיבית‪.‬‬
‫ איטרטיבי‬- ‫סדרת פיבונאצ'י‬
int fib(int n)
{
int i, next, fib1 = 1, fib2 = 1;
if (n == 1 ||
return n-1;
n == 2)
for (i = 3; i <= n; i++)
{
next = fib1 + fib2;
fib1 = fib2;
fib2 = next;
}
return fib2;
}
‫שימוש ברקורסיה‬
‫• מצד אחד‪:‬‬
‫• לפעמים לשימוש ברקורסיה יש יתרון ‪ -‬קל יותר לכתוב‬
‫באמצעותו את החישוב‬
‫• מצד שני‪:‬‬
‫• לא תמיד קל למצוא הגדרה רקורסיבית לפונקציה‬
‫• לא תמיד קל להבין תוכניות שנכתבו באופן רקורסיבי‬
‫• לכן נבחר להשתמש ברקורסיה במקרים שבאמת נוחים לכך‪.‬‬
‫שאלות?‬