פונקציות תכנות בשפת סי תרגול 7
הגדרה: המונח רקורסיה (recursion) מתאר מצב שבו פונקציה קוראת לעצמה באופן ישיר או באופן עקיף. שימוש: נוח להשתמש בפונקציות רקורסיביות ע"מ לפתור בעיות בעלות אופי רקורסיבי. באופן כללי, השיטה תהיה להקטין את מימד הבעיה, לפתור את הבעיה על המימד היותר קטן ולהשתמש בפיתרון שמתקבל ע"מ לפתור את הבעיה במימד אחד יותר גבוהה. רקורסיה
דוגמה מס ' 1- חישוב עצרת : א) חישוב n! באופן איטרטיבי: int iterFactorial(int n) { int res=1; while(n>0) { res*=n; n--; } return res; } ב) חישוב n! באופן רקורסיבי: int recFactorial(int n) { if(n<=1) return 1; return n*recFactorial(n-1); }
דוגמה מס ' 1- חישוב עצרת : בעת שימוש בפונקציה זו לחישוב 3!, תרשים הסביבות יראה כך : recFactorial(3) n=3 if(n<=1) return 1; return 3* recFactorial(2) void main() { n=2 if(n<=1) return 1; return 2* recFactorial(1) n=1 if(n<=1) return 1;
תזכורת – משתנים סטאטיים מגדירים באופן הבא: static int var; ניתן לאתחל בשורת ההגדרה בלבד כאשר חייבים לאתחל בביטויי קבוע!!! אם אנחנו לא מאתחלים אותם, המערכת תאתחל אותם אוטומטית לאפס. הscope- של משתנים סטאטיים הוא רק הפונקציה שבה הם הוגדרו אך הם ממשיכים להתקיים גם אחרי שביצוע הפונקציה נגמר (למעשה עד סוף ריצת התוכנית). לפיכך הערך של משתנה סטאטי בפונקציה כלשהי נשמר בין קריאות עוקבות לפונקציה זו.
דוגמה מס ' 2 – harmonic number מס ' הרמוני של המס ' השלם n מוגדר באופן הבא : ברצוננו לכתוב פונקציה רקורסיבית rec_harmonic_num(n) אשר תדפיס למסך את הפיתוח של h(n). למשל, עבור הקריאה rec_harmonic_num(5), נקבל על המסך : 1/5+1/4+1/3+1/2+1=2.28
דוגמה מס ' 2 – harmonic number void rec_harmonic_sum(int n) { static double sum=1; if(n==1)/*Stop condition*/ { printf("1=%.2f\n",sum); sum=1; /*Must initialize the static variable "sum" so that we can call this function repeatedly in the same program run. Make sure you understand why!!! */ return; } printf("1/%d+",n); sum+=1./n; /*The "1." syntax is used in order to prevent automatic casting to integer type (so that the expression "1/n" will be evaluated as type double). */ rec_harmonic_sum(n-1); /*The recursive call. */ }
דוגמה מס ' 3 – abc כתוב פונקציה רקורסיבית בשם void abc(char arr[],int lastPlace, int curPlace), המקבלת מערך של char-ים, את אינדקס סוף המערך ומספר שלם שהוא המקום במערך ממנו אנו מעונינים להתחיל במילוי המערך בתווים (בקריאה ראשונה יהיה מס' זה שווה ל- 0). הפונקציה מדפיסה את כל האפשרויות למלא את המערך מהמקום שקיבלה, curPlace, עד המקום lastPlace באותיות a,b,c במילים אחרות, מטרת הפונקציה היא להדפיס את כל האפשרויות ליצור מילה באורך מסוים בעזרת האותיות a,b,c.
דוגמה מס ' 3 – abc למשל: עבור התוכנית הבאה: void abcInvokingFun(char word[],int lengthOfWord) { abc(word, lengthOfWord,0); } void main() { char word[5]; abcInvokingFun(word,3); } נקבל את הפלט: aaaaabaacabaabbabcacaacbaccbaa bab bacbbabbbbbcbcabcbbcccaacabcac cba cbbcbcccaccbccc
כתובתו של תחילת המערך המקום הנוכחי שאנחנו משנים תנאי העצירה : אם הגענו ל - lastPlace אז שים במקום הנוכחי יש '0\' ( הגענו לסוף ), הדפס את המחרוזת ( מהתחלתה ) וחזור. כעת אנחנו עובדים על המקום curPlace במערך : הצב בו a, וקרא לפונק ' abc ( עם כתובת המערך, ואינדקס המקום הבא ) אשר תדאג למילוי כל האפשרויות הקימות בשאר המערך. לאחר שחזרת, הצב את b במקום a, ושוב קרא לפונק ' עם המקום הבא במערך כפרמטר. כנ " ל לגבי c. אינדקס סוף המערך void abc(char arr[],int lastPlace, int curPlace) { if (curPlace == lastPlace) { arr[curPlace]='\0'; printf("%s\t",arr); return; } arr[curPlace] = 'a'; abc (arr,lastPlace,curPlace+1); arr[curPlace] = 'b'; abc (arr,lastPlace,curPlace+1); arr[curPlace] = 'c'; abc (arr,lastPlace,curPlace+1); } דוגמה מס ' 3 – abc
curPlace=2 curPlace=0 curPlace=3 curPlace=1 נראה את תרשים הקריאות המתקבל מהרצת הדוגמא שלמעלה ( בדומה לתרשים הקריאות שראינו בכתה ): כל ריבוע מייצג קריאה לפונקציה, כאשר בתוך כל ריבוע מופיע תוכן המערך שהפונקציה מקבלת. ערך הפרמטר curPlace הוא משותף לכל רמה בתרשים ומצויין בצד ימין.
תרגיל מס ' 1 נתונה הפונקציה הרקוסיבית הבאה: #include int secret( int n) { if( n<0 ) return 1 + secret ( -1 * n); if ( n<10 ) return 1; return 1 + secret( n/10 ); } מה הערך של secret(-4321) ו- secret(12345) ? פתרון: 5 בשני המקרים.
תרגיל מס ' 1 הסבר בקצרה מה מבצעת הפונקציה secret עבור פרמטר חיובי ועבור פרמטר שלילי. פתרון : הפונקציה מחזירה את מס ' התווים שמייצגים את הארגומנט. כך למשל הפלט של התוכנית הבאה : void main() { char s[80]; printf("%d\n",secret(-43221)); printf("%d\n",secret(12345)); יהיה :6 5 }
תרגיל מס ' 1 כתוב אותה פונקציה בצורה לא רקורסיבית. פתרון: int iterSecret( int n) { int len=1; if( n<0 ) { n=(-1) * n; len++; } while( n>=10 ) { n/=10; len++; } return len; }
תרגיל מס ' 2 עיין בפונקציה הבאה: int what(int a, int b) { if(!a && !b) return 1; if(a > b)return a * what(a-1, b); return b * what(a, b-1); } בהנחה שהפונקציה הנ"ל מקבלת שני ערכים אי-שליליים (חיוביים או אפס), סמן את כל התשובות הנכונות (בדף התשובות): 1.הפונקציה נכנסת לרקורסיה אינסופית. 2.הערך המוחזר של הפונקציה תמיד 0. 3.הערך המוחזר של הפונקציה יכול להיות 0. 4.הערך המוחזר של הפונקציה תמיד 1. 5.הערך המוחזר של הפונקציה יכול להיות 1. 6.בקבלת שני ערכים חיוביים a ו- b, אם הערכים לא גדולים מידי,הפונקציה מחזירה את הערך של a! x b!. 7. אף לא אחת מהתשובות לעיל. פתרון: 5 (למשל כאשר a=0 וגם b=0) + 6 (יש להדגים למשל עבור a=2 וגם b=1 )
תרגיל מס ' 3Subset Sum Problem arr נתונה סדרת ערכים שלמים (כמערך arr ומספר שלם S ) צ " ל : האם קיימת תת - סדרה במערך כך שסכומה S למשל : ו - S=14. התשובה היא כן כי קיימת תת - סדרה במערך שהיא : 7,6,1 וסכומה 14. האלגוריתם : יש לנו כאן 2 אפשרויות עבור כל איבר i במערך : אפשרות א ': לוקחים את האיבר ה - i ומנסים למצוא SubsetSum בגודל S-arr[ i ] במערך קטן יותר ב -1. אפשרות ב ': לא לוקחים את האיבר ה - i ומנסים למצוא SubsetSum בגודל S במערך קטן יותר ב -1. תנאי העצירה שלנו יהיו : 1) אם קיבלנו באיזשהו שלב S==0 אזי יש לנו SubsetSum במערך ונחזיר 1. 2) אחרת אם קיבלנו S<0 או 0==n אזי הבחירות שלקחנו עד עכשיו אינן מובילות לפתרון ( אם S<0 אזי עברנו את הסכום המבוקש ואם 0==n אזי עדיין לא הגענו לסכום ואין לרשותנו אברים מתוכם נוכל לבחור ) ונחזיר 0.
תרגיל מס ' 3 טיפול באפשרות בטיפול באפשרות א ' int SubsetSum(int arr[], int n, int S) { if (0==S) return 1; //This is stopping condition #1. if (S<0 || 0==n) return 0; //This is stopping condition #2. return SubsetSum(arr+1,n-1,S) || SubsetSum(arr+1,n-1,S-arr[0]); } איך ניתן למצא את תת-הסדרה שסכום איבריה הינו S, בהנחה שזו אכן קיימת? נעבור פעם אחת על המערך ונשאל עבור כל איבר i במערך האם יש SubsetSum בגודל S במערך [[i+1,...,n. אם יש אזי לא ניקח את האיבר ה-i ונמשיך לאיבר הבא עם S. אם אין אזי האבר ה-i בטוח ב-SubsetSum,לכן ניקח אותו ונמשיך לאיבר הבא עם S=S-arr[i].
תת - הסידרה שאגרנו עד כה !!! האינדקס של subseq שגם מציין את מס ' האיברים שיש בה ברגע ההדפסה !!! להלן תכנית המדפיסה את כל תתי - הסדרות האפשריות : #include void printArr(int arr[], int n) { int i ; puts(""); for (i=0; i<n; i++) printf (" %d",arr[i]); puts(""); } int SubsetSum(int arr[], int n, int S, int subseq[],int count) { int withCurrent, withoutCurrent; if (S==0) { printArr(subseq,count); return 1; } if (S< 0 || !n) return 0; subseq[count] = arr[0]; withCurrent = SubsetSum(arr+1,n-1,S-arr[0],subseq, count+1); withoutCurrent= SubsetSum(arr+1,n-1,S,subseq,count); return (withoutCurrent || withCurrent); } void main() { int arr[] = {7, 6, 5,1,7,13,17}, subseq[8]; int S = 14,arrSize = 7; if (!SubsetSum(arr,arrSize,S,subseq,0)) printf("\nThere is no subsequence\n"); }
תרגיל מס ' 4 רקורסיה עם backtracking backtracking – דרך רקורסיבית לפתרון בעיות באמצעות בדיקה שיטתית של כל הפתרונות האפשריים. את הפתרונות הלא נכונים אנו פוסלים ולא מנסים יותר מפעם אחת. בסוף אנו נותרים עם הפתרון הנכון. נסתכל על הדוגמה הבאה : הציור מייצג מבוך. נניח והיינו רוצים להגיע מחדר A לחדר B. כדי להבין את שיטת ה -Backtrack נתאר דרך לפתרון הבעיה ( בכל חדר אנו רואים רק את החדרים אליהם ניתן להגיע ואין אנו רואים את המבוך כולו ). 1. עבור מחדר A לחדר בחדר 2 ישנם שתי אפשרויות. נבחר לעבור לחדר הגענו למבוי סתום, נחזור לחדר 2. ובחדר 2 נבחר הפעם לעבור לחדר נעבור לחדר 4. ( כי אין ברירה אחרת ). 5. בחדר 4 ישנם שתי אפשרויות. נבחר למשל, לעבור לחדר שוב פעם שתי אפשריות נבחר בחדר הגענו למבוי סתום. נחזור לחדר 6 ושם נבחר לעבור לחדר הגענו למבוי סתום. נחזור לחדר 6 ונבין שמימשנו את כל האפשרויות להתקדם דרך חדר 6 לכן נחזור לחדר נתקדם לחדר נתקדם ל -B. וסיימנו את המבוך. ניתן למשל לייצג מבוך ב -C ע " י מערך שלמים דו - מימדי. התא ה -( i, j ) של המטריצה מייצג קיום ( ערך 1) או אי קיום ( ערך 0) מעבר בין חדר i וחדר j.
תרגיל מס ' 4 #include #define N 10 #define no_pass 0 #define yes_pass 1 #define not_visited 0 /*This function tells us wether we can move from i to j*/ int canGo (int maze[N][N],int i,int j) { return maze[i][j]; }
תרגיל מס ' 4 int find_way(int maze[N][N],int curRoom) { static int visit[N]={not_visited}; int nextRoom; if (visit[curRoom]) /*So that we don't walk around in circles! */ return 0; visit[curRoom]=1; /*Mark that we have already visited this chamber. */ if (curRoom==0) /*If we found a way from the begining point, A. */ { printf("There is a path and it is:\n A=>"); return 1; } /*Run over all rooms smaller than the last one, and if there is a passage from the current room to them, find a way from them to the destination point B. If there is a way from them, add the current room to the path and return 1. */ for(nextRoom = N-2; nextRoom >= 0; nextRoom--) { /*Check if there is a passage from the current room to the next one: */ if (canGo(maze,curRoom,nextRoom)) /*Check if there is way to get from the next room to B, if so return 1: */ if (find_way(maze,nextRoom)) { printf("%d=>",curRoom); return 1; } return 0; /*If there is no way from the current room to B. */ }
תרגיל מס ' 4 void main() { int maze[N][N] = {no_pass}; maze[0][2] = yes_pass; maze[1][2] = yes_pass; maze[2][0] = yes_pass; maze[2][1] = yes_pass; maze[2][3] = yes_pass; maze[3][2] = yes_pass; maze[3][4] = yes_pass; maze[4][3] = yes_pass; maze[4][5] = yes_pass; maze[4][6] = yes_pass; maze[5][4] = yes_pass; maze[5][9] = yes_pass; maze[6][4] = yes_pass; maze[6][7] = yes_pass; maze[6][8] = yes_pass; maze[7][6] = yes_pass; maze[8][6] = yes_pass; maze[9][5] = yes_pass; if (find_way(maze,N-1)) printf("B.\n"); else printf("There is no path\n"); }