Presentation is loading. Please wait.

Presentation is loading. Please wait.

תרגול 5 רקורסיות.

Similar presentations


Presentation on theme: "תרגול 5 רקורסיות."— Presentation transcript:

1 תרגול 5 רקורסיות

2 היום בתרגול דוגמאות לרקורסיה Subset-Sum המבוך

3 מבוא למדעי המחשב, בן גוריון תשע"א
רקורסיה דוגמה 5 – הסדרה ההרמונית*: הסדרה ההרמונית היא הסדרה . הסבר מתחום המוסיקה: הסדרה קרויה כך בגלל הצלילים העיליים (אוברטונים, הרמוניות). אורכי המיתרים שיוצרים את הצלילים העיליים פרופורציונליים לסדרה אחת, חצי, שליש וכו'. כתוב תוכנית שתחשב את הסכום של n איברים מהטור ההרמוני. public static double harmony(int n) { double dAns; if (n == 1) dAns = 1.0; else dAns = harmony(n-1) + 1.0/n; return dAns; } מבוא למדעי המחשב, בן גוריון תשע"א

4 דוגמא 2 נכתוב פונקציה רקורסיבית שמציירת משולש הפוך בגובה של n שורות.
******* ****** ***** **** *** ** * דרך פעולה: נדפיס שורת כוכביות באורך n, ואז נדפיס משולש בגובה n-1. תנאי העצירה יהיה כאשר נגיע להדפיס משולש בגובה 0.

5 והפתרון: public static void drawTriangle(int n) { int i;
if (n > 0) { for (i=0; i<n; i=i+1) System.out.print('*'); System.out.println(); drawTriangle(n-1); } *** ** * עבור המקרה בו קוראים לפונקציה עם n=3: רקורסית זנב? כן!

6 public static void drawTriangle(int n) { int i; if (n > 0) {
* ** *** **** ***** ****** ******* מה היה קורה אם היינו רוצים להדפיס משולש ישר?? היינו צריכים קודם לקרוא ל- drawTriangle עם n-1, ואז להדפיס שורה של כוכביות באורך n: public static void drawTriangle(int n) { int i; if (n > 0) { drawTriangle(n-1); for (i=0; i<n; i=i+1) System.out.print('*'); System.out.println(); }

7 public static void drawHourGlass (int n) { int i; if (n > 0) {
אם נשלב את שני החלקים: public static void drawHourGlass (int n) { int i; if (n > 0) { for (i=0; i<n; i=i+1) System.out.print('*'); System.out.println(); drawHourGlass(n-1); } נקבל: ***** **** *** ** *

8 דוגמא 2 כתבו פונקציה רקורסיבית שמקבלת מחרוזת ומחזירה true אם היא פָּלִינְדְרוֹם ו- false אחרת. תזכורת: פָלִינְדְרוֹם היא מחרוזת שניתן לקרוא משני הכיוונים, משמאל לימין ומימין לשמאל, ולקבל אותה תוצאה. לדוגמא: המחרוזות "ארון קיר היה ריק נורא", "ילד כותב בתוך דלי" ו- madam הן פָלִינְדְרוֹם, לעומת זאת המחרוזת hello אינה פָלִינְדְרוֹם. הנחת יסוד: מחרוזת ריקה (ללא תווים) ומחרוזת בעלת תו אחד הן פָלִינְדְרוֹם.

9 דוגמא 2 – דרך פעולה דרך פעולה: מקרה הבסיס:
אם מדובר במחרוזת ריקה (ללא תווים) או במחרוזת בעלת תו אחד ניתן להחזיר true . אחרת, נבדוק עבור מחרוזת קטנה יותר (הקטנת הבעיה) ע"י צמצום המחרוזת בתו אחד מכל צד. אם הפעלת הפונקציה הרקורסיבית על המחרוזת המוקטנת תחזיר true וגם שני התווים הקיצוניים שהורדנו מהמחרוזת המקורית שווים נחזיר true, אחרת נחזיר false.

10 והפתרון: public static boolean isPalindrome(String pal) {
boolean isPal = false; int length = pal.length(); if (length == 0 || length == 1) // can be “if (length <= 1)” instead isPal = true; else { isPal = (pal.charAt(0)==pal.charAt(length-1) && isPalindrome(pal.substring(1,length-1))); } return isPal;

11 בעיית Subset Sum (SUSU)
בהנתן מערך של משקולות ומשקל נוסף, נרצה לבדוק האם ניתן להרכיב מהמשקולות משקל השווה למשקל הנתון. דוגמא לקלט: weights={1,7,9,3} Sum = 12 במקרה זה הפונקציה תחזיר true כי ניתן לחבר את המשקולות 9 ו 3 ולקבל את הסכום 12.  מה יוחזר עבור sum=15? תשובה: false

12 אסטרטגיית הפתרון נעבור על כל האיברים במערך ונבחן אפשרויות בהן איבר נבחר או לא נבחר. נתבונן באיבר הראשון במערך. ייתכן שהוא ייבחר לקבוצת המשקולות שתרכיב את sum ויתכן שלא. אם הוא לא ייבחר – אזי נותר לפתור בעיה קטנה יותר והיא: האם ניתן להרכיב את הסכום sum מבין המשקולות שבתאים weights[1,…,n-1] אם הוא ייבחר – אזי נותר לפתור בעיה קטנה יותר והיא: האם ניתן להרכיב את הסכום sum-weight[0] מבין המשקולות שבתאים weights[1,…, n-1]. כנ"ל לגבי יתר האיברים בצורה רקורסיבית. 

13 אסטרטגיית הפתרון – המשך
נעבור על כל האיברים במערך ונבחן אפשרויות בהן איבר נבחר או לא נבחר. קיימים שני מקרי בסיס: הגענו לסכום הדרוש או במילים אחרות הסכום הנותר הינו אפס. הגענו לסוף המערך – עברנו על כל האיברים ולא מצאנו צירוף של איברים שסכומם שווה לסכום הנדרש.

14 פתרון פתרון זה קל להציג כפונקציה רקורסיבית: calcWeights(int[] weights, int i, int sum) אשר מקבלת בנוסף על sum ו weights פרמטר נוסף i שמייצג את המשקולת שבודקים כעת ומחזירה תשובה לשאלה: האם ניתן להרכיב את הסכום מבין קבוצת המשקולות שבתת המערך weights[i…weights.length]. public static boolean calcWeights(int[] weights, int sum) { return calcWeights(weights, 0, sum); }

15 פתרון (המשך) public static boolean calcWeights(int[] weights, int i, int sum) { boolean res = false; if (sum == 0) res = true; else if (i >= weights.length) res = false; else res =( calcWeights(weights,i+1,sum-weights[i]) || calcWeights(weights, i + 1, sum) ); return res; }

16 דוגמא 4 מטריצה משופעת: מטריצה לא משופעת: 2 4 1 7 5 -2 3 -1 -8 -6 -9 -3
מטריצה תקרא משופעת אם: כל איברי האלכסון הראשי שווים לאפס. כל האיברים שנמצאים מתחת לאלכסון הראשי הם שליליים. כל האיברים שנמצאים מעל לאלכסון הראשי הם חיוביים. (מניחים כי המימד הראשי מגדיר את השורות והמשני מגדיר את העמודות). 2 4 1 7 5 -2 3 -1 -8 -6 -9 -3 מטריצה משופעת: מטריצה לא משופעת: 2 4 1 7 5 -2 3 6 -1 -8 -6 9 -3

17 דוגמא 4 – הרעיון נשים לב שאם נחסיר ממטריצה משופעת את השורה הראשונה ואת העמודה הראשונה, נקבל מטריצה משופעת. נשאר לבדוק האם השורה והעמודה שהחסרנו מקיימות את התנאים. 4 1 5 -2 -1 -8 5 -1

18 דוגמא 4 –המשך נניח שקיימת פונקציה check המקבלת מטריצה ריבועית ואינדקס של שורה/עמודה ומחזירה האם השורה והעמודה מקיימות את התנאים. כתבו פונקציה רקורסיבית המקבלת מטריצה מלאה במספרים, ומחזירה true אם היא משופעת ו-false אחרת: p.s. boolean slope(int[][] data);

19 דוגמא 4 –המשך 4 1 5 -2 -1 -8 5 -1 דרך פעולה:
בכל שלב של הרקורסיה נתייחס למטריצה הקטנה יותר (נתקדם לאורך האלכסון) ונבדוק אם תת המטריצה משופעת וגם שאר התנאים עבור המטריצה הנוכחית מתקיימים . מהו מקרה הבסיס? ומה נבצע עבורו? תת המערך בגודל 1x1 ולכן נבדוק אם מכיל אפס. 4 1 5 -2 -1 -8 5 -1

20 והפתרון: public static boolean slope(int[][] data) {
return slope(data, 0); } public static boolean slope(int[][] data, int index) { boolean isSlope = false; //end of array – last cell, if it’s 0 then it’s OK if (index == data.length - 1) isSlope = (data[index][index] == 0); else isSlope = (check(data,index) && slope(data, index+1)); return isSlope;

21 ניתן לבצע זאת גם בצורה רקורסיבית.
דוגמא 4 –המשך איך נראת הפונקציה check? /* Check if row index, in data array, contains positive numbers and column index contains negative numbers (starting from index to the right & down) */ public static boolean check(int[][] data, int index) { boolean flag = (data[index][index] == 0); for (int i=1;(i<data.length-index) && flag; i=i+1) { if (data[index][index+i]<=0 || data[index+i][index]>=0) flag = false; } return flag; ניתן לבצע זאת גם בצורה רקורסיבית. כיצד?

22 דוגמה 5 - המבוך

23 המבוך נתון מערך דו-מימדי n x m של שלמים, בשם grid, המתאר מבוך:
נקודת ההתחלה היא התא במיקום (0,0) במערך, נקודת הסיום היא התא במיקום (n-1,m-1), במצב ההתחלתי לכל תא ערך 0 או 1, כאשר 1 מסמל "דרך פנויה" ו-0 מסמל "דרך חסומה" (קיר). הפיתרון הרצוי הוא מסלול רציף מנקודת ההתחלה לנקודת הסיום (מותר ללכת למטה, ימינה, למעלה ושמאלה). את המסלול נסמן בעזרת החלפת ה- 1-ים שעל המסלול, ב- 7.

24 המבוך - דוגמא מבוכים: בפיתרון: נסמן ב- ערך 3 את התאים שבהם כבר ביקרנו כדי שלא נחזור אליהם שנית. נסמן ב-7 את הדרך מההתחלה אל הסיום. מבוך ללא פתרון מה יכול להתרחש אם נטייל על המסלול, ולא נבדוק האם ביקרנו כבר בקוביה אליה אנו הולכים?

25 המבוך בפיתרון: נסמן ב-3 את התאים שבהם כבר ביקרנו כדי שלא נחזור אליהם שנית. נסמן ב-7 את הדרך מההתחלה אל הסיום. מבוך:

26 המבוך פתרון רצוי: הסבר: איך חושבים על זה בצורה רקורסיבית. ננסה מהקובייה הנוכחית להסתכל על הקוביות השכנות ולראות אם יש דרך מאחת מהן. אם כן – הדרך מהקובייה הנוכחית היא הדרך המתקבלת ע"י הוספת הקובייה הנוכחית לתחילת הדרך שבה ניתן להגיע מהקובייה השכנה.

27 איך חושבים על זה בצורה רקורסיבית?
ננסה מהקוביה הנוכחית להסתכל על הקוביות השכנות ולראות אם יש דרך מאחת מהן. אם כן – הדרך מהקוביה הנוכחית היא הדרך המתקבלת ע"י הוספת הקוביה הנוכחית לתחילת הדרך שבה ניתן להגיע מהקוביה השכנה. המשימה שלנו היא למצוא מעבר מנקודת ההתחלה לנקודת הסיום, אך האלגוריתם שנתאר אינו מתחיל דווקא בנקודת ההתחלה, אלא ימצא את הדרך ליציאה מכל נקודה במבוך. solve(int[][] grid, int row, int col)

28 תיאור האלגוריתם הקריאה הראשונה לאלגוריתם תעביר כקלט את הכניסה בתור שורה 0 וטור 0. solve(grid, 0, 0) האלגוריתם נעזר בפונקציה valid(grid, row, col) המקבלת כקלט מבוך, שורה וטור, ומחזירה ערך בוליאני – true אם התא במבוך המוגדר ע"י השורה והטור הוא תא חוקי (כלומר לא חורג מגבולות המבוך), פנוי ולא ביקרנו בו כבר (כלומר, מסומן ב-1), ו-false אחרת. public static void main(String[] args) { int[][] grid = {…}; boolean ans = solve(grid, 0, 0); if (ans) System.out.println("Maze solved!:\n"); else System.out.println("There is no solution"); }

29 תיאור solve(grid, row, col)
הגדר משתנה בוליאני בשם done ואתחל אותו ל- false אם ( valid(grid, row, col) ) 2.1. סמן ב- grid את תא [row][col] במספר 3 /* סימון שביקרנו בתא ואין צורך לבקר בו שוב */ 2.2. אם row היא השורה התחתונה ב-grid וגם col הוא הטור הימני ב- grid, שנה את ערכו של done ל-true /* תנאי עצירה */ 2.3. אחרת, Done מסמן שהגענו לנקודת היציאה. ומצאנו דרך במבוך.

30 תיאור solve(grid, row, col)
שנה את ערכו של done ל- solve(grid, row+1, col) /* נסה למטה */ אם ( done הינו false ), שנה את ערכו של done ל- solve(grid, row, col+1) /* נסה ימינה */ אם ( done הינו false ), שנה את ערכו של done ל-solve(grid, row-1, col) /* נסה למעלה */ אם ( done הינו false ), שנה את ערכו של done ל-solve(grid, row, col-1) /* נסה שמאלה */ 2.4. אם ( done הינו true ), סמן ב- grid את תא [row][col] במספר 7 /* תא זה הוא חלק מהפתרון */ 3. החזר את done.

31 public static boolean solve(int[][] grid, int row, int col) {
boolean done = false; if (valid(grid, row, col)) { grid[row][col] = 3; // mark visted if ((row == grid.length-1) && (col == grid[0].length-1)) done = true; // maze is solved else { done=( solve(grid, row + 1, col) || // try down solve(grid, row, col+1) || // try right solve(grid, row-1, col) || // try up solve(grid, row, col-1) ); // try left } if (done) grid[row][col] = 7; // mark as part of the path return done;

32 דוגמת הרצה מבוך: 3 3 נק' התחלה 3 נסה למטה נסה למטה נסה ימינה נסה למעלה נסה שמאלה חזור צעד אחד אחורה נסה ימינה ...

33 בשלב כלשהו בריצה, אנו נמצאים בקריאה אשר סימנה ב-3 את התא המודגש: (row=3, col=6)
בשלב זה תהיה קריאה רקורסיבית על המשבצת מתחתיה, משבצת המסומנת ב-0, כלומר קיר. בדיקת ה-valid תחזיר false ואותה קריאה תסתיים.

34 תתבצע קריאה נוספת מהמשבצת המסומנת, הפעם ימינה, ובדיקת ה-valid תיכשל שוב.
בקריאה הבאה מהמשבצת המסומנת, הפעם למעלה, בדיקת ה-valid תחזיר true

35 הקריאה הרקורסיבית במשבצת העליונה תסמנה ב-3 והריצה תמשיך.

36 הקריאה הבאה מתבצעת למשבצת משמאל עם הערך 1 (עם קו תחתון)
זו התמונה אחרי כמה קריאות. בשלב זה תתבצע קריאה מהמשבצת המסומנת באדום והיא נכשלת. הקריאות חוזרות. הקריאה הבאה מתבצעת למשבצת משמאל עם הערך 1 (עם קו תחתון) נכשלנו: נחזור חזרה בכל קריאה ונחזיר false 3

37 בתום הקריאה הרקורסיבית במשבצת המסומנת במרובע ה – grid יראה כך
בתום הקריאה הרקורסיבית במשבצת המסומנת במרובע ה – grid יראה כך. הקריאה הרקורסיבית במשבצת זו תחזיר ערך true. במסגרת הקוראת (הקריאה במשבצת המסומנת באדום) תסמן גם 7 ותחזיר true. המשבצת הימנית לא תיבדק. 7

38 בסיום הריצה, כשהגענו אל היציאה, הדרך תסומן ב-7 והמבוך יראה כך:
ונחזיר true דיון: האם תמיד יש פתרון אחד? אם לא, איזה פתרון נמצא? מה היה יכול לקרות אם לא היינו מסמנים כל תא שבדקנו (כאן סימון זה היה המספר 3)?

39 דוגמא 6 כתבו פונקציה רקורסיבית שמקבלת מחרוזת ומחזירה את המחרוזת הפוכה.
לדוגמא: עבור הקלט “hello” הפונקציה תחזיר את הפלט “olleh”. דרך פעולה: מקרה הבסיס: אם מדובר במחרוזת ריקה (ללא תווים) או במחרוזת בעלת תו אחד ניתן להחזיר את התו עצמו (ובמקרה של ריקה להחזיר ""( אחרת, נמצא את המחרוזת ההפוכה של מחרוזת קטנה יותר (הקטנת הבעיה) ע"י צמצום המחרוזת בתו אחד השמאלי, ונוסיף את התו השמאלי מימין למחרוזת ההפוכה שנקבל מהקריאה הרקורסיבית.

40 והפתרון: public static String reverse(String s){ String res = "";
if (s.length()==0) res = s; else res = reverse(s.substring(1)) + s.charAt(0); return res; } שימו לב: הפונקציה s.substring(i) מחזירה את המחרוזת s ממקום i. לא! (בכיתה ראינו דוגמא למימוש עם רקורסיית זנב) רקורסית זנב?

41 מבוא למדעי המחשב, בן גוריון תשע"א
רקורסיה דוגמה 4 – פונקציה מסתורית*:  מצא מה התוכנית הבאה מחשבת. public static int mystery(int a, int b) { int iAns; if (b == 0) iAns = 0; else if (b % 2 == 0) iAns = mystery(a+a, b/2); iAns = mystery(a+a, b/2) + a; return iAns; } mystery(4,10) mystery(8,5) mystery(16,2)+8 mystery(32,1)+8 mystery(64,0)+8+32 0+40 40=4*10 תשובה: a*b. ניתן להדגים עם mystery(4,10) מבוא למדעי המחשב, בן גוריון תשע"א

42 מבוא למדעי המחשב, בן גוריון תשע"א
רקורסיה מצא מה התוכנית הבאה עושה ? public static int mystery(int a, int b) { int iAns; if (b == 0) iAns = 1; else if (b % 2 == 0) iAns = mystery(a*a, b/2); iAns = mystery(a*a, b/2) * a; return iAns; } mystery(2,5) mystery(4,2)*2 mystery(16,1)*2 mystery(256,0)*32 1*32 32=2^5 תשובה: ab. ניתן להדגים עם mystery(2,5) מבוא למדעי המחשב, בן גוריון תשע"א


Download ppt "תרגול 5 רקורסיות."

Similar presentations


Ads by Google