דוגמא
Transcription
דוגמא
תרגול מספר 6 רקורסיות נושאי התרגול: .1 .2 .3 .4 .5 .6 דוגמא :1הדפסת שעון חול דוגמא :2פלינדרום דוגמא :3מציאת משקל נתון ע"י צירוף משקלות דוגמא :4מטריצה משופעת דוגמא :5המבוך דוגמא :6הפיכת מחרוזת דוגמא :1 נכתוב פונקציה רקורסיבית שמציירת משולש הפוך בגובה של nשורות. לדוגמא ,עבור n=7נקבל את המשולש: ******* ****** ***** **** *** ** * דרך פעולה נדפיס nכוכביות בשורה ,ואז נדפיס משולש בגובה .n-1 תנאי העצירה יהיה כאשר נגיע להדפיס משולש בגובה .0 נדפיס משולש בגובה n-1ונוסיף עוד שורת כוכביות באורך .n הקוד { )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 } } Page 1 Practical session #6 ??מה היה קורה אם היינו רוצים להדפיס משולש ישר :n ואז להדפיס שורה של כוכביות באורך,n-1 עםdrawTriangle -היינו צריכים קודם לקרוא ל public static void drawTriangle2(int n) { int i; if (n > 0) { drawTriangle2(n-1); for (i=0; i<n; i=i+1) System.out.print('*'); System.out.println(); } } ניתן להבחין שהפונקציה הראשונה הינה רקורסיית זנב היות והשורה אחרונה של הפונקציה היא הקריאה .הרקורסיבית ?מה יקרה אם נשלב בין שני החלקים 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); for (i=0; i<n; i=i+1) System.out.print('*'); System.out.println(); } } :נקבל ***** **** *** ** * * ** *** **** ***** Practical session #6 Page 2 דוגמא :2 כתבו פונקציה רקורסיבית שמקבלת מחרוזת ומחזירה trueאם היא פָ לִינְדְ רֹום ו false -אחרת )public static boolean isPalindrome (String pal תזכורת :פָ לִינְדְ רֹום היא מחרוזת שניתן לקרוא משני הכיוונים ,משמאל לימין ומימין לשמאל ,ולקבל אותה מילה. דוגמא :המחרוזות madamו noon-הן פָ לִינְדְ רֹום (וגם "ילד כותב בתוך דלי") ,לעומת זאת המחרוזת helloאינה פָ לִינְדְ רֹום. הנחת יסוד :מחרוזת ריקה (ללא תווים) ומחרוזת בעלת תו אחד הן ָפלִינְדְ רֹום. דרך פעולה מקרה הבסיס: אם מדובר במחרוזת ריקה (ללא תווים) או במחרוזת בעלת תו אחד ניתן להחזיר ,true אחרת ,נבדוק עבור מחרוזת קטנה יותר (הקטנת הבעיה) ע"י צמצום המחרוזת בתו אחד מכל צד אם הפעלת הפונקציה הרקורסיבית על המחרוזת המוקטנת תחזיר trueוגם שני התווים הקיצוניים שהורדנו מהמחרוזת המקורית שווים ,נחזיר ,trueאחרת נחזיר .false הקוד { )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 } דוגמא :3 בהינתן מערך של משקולות ומשקל נוסף ,נרצה לבדוק האם ניתן להרכיב מהמשקולות משקל השווה למשקל הנתון. דוגמא לקלט: }weights={1,7,9,3 Sum = 12 במקרה זה הפונקציה תחזיר trueכי ניתן לחבר את המשקולות 9ו 3ולקבל את הסכום .12 דוגמא לקלט: }weights={1,7,9,3 Sum = 15 במקרה זה הפונקציה תחזיר falseכי לא ניתן לחבר משקולות לקבלת הסכום .15 Page 3 Practical session #6 דרך פעולה קיימים שני מקרי בסיס: .1הגענו לסכום הדרוש או במילים אחרות הסכום הנותר הינו אפס. הגענו לסוף המערך – עברנו על כל האיברים ולא מצאנו צירוף של איברים שסכומם שווה .2 לסכום הנדרש. נתבונן באיבר הראשון במערך .ייתכן שהוא ייבחר לקבוצת המשקולות שתרכיב את sumויתכן שלא. אם הוא לא ייבחר – אזי נותר לפתור בעיה קטנה יותר והיא האם ניתן להרכיב את הסכום sumמבין המשקולות שבתאים ]weights[1..length - 1 אם הוא ייבחר – אזי נותר לפתור בעיה קטנה יותר והיא האם ניתן להרכיב את הסכום ] sum weights[0מבין המשקולות שבתאים weights[1..length - 1] . וכנ"ל לגבי יתר האיברים בצורה רקורסיבית. פתרון זה קל נותר להציג כפונקציה רקורסיבית ) , calcWeight s(int[] weights ,int i, int sumאשר מקבלת בנוסף על sumו weightsפרמטר נוסף iומחזירה האם ניתן להרכיב את הסכום sumמבין קבוצת המשקולות שבתת המערך ]weights [i..length 1 הקוד { )public static boolean calcWeights(int[] weights, int sum ;)return calcWeights(weights, 0, sum } { )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 } עבור כל משקולת יש את האפשרות לבחור אותה לסכום או לא לבחור אותה .עובדה זו באה לידי ביטוי בקריאה הרקורסיבית. Page 4 Practical session #6 דוגמא :4 מטריצה ריבועית תקרא משופעת אם: .1כל איברי האלכסון הראשי שווים לאפס. .2כל האיברים שנמצאים מתחת לאלכסון הראשי הם שליליים. .3כל האיברים שנמצאים מעל לאלכסון הראשי הם חיוביים. כתבו פונקציה רקורסיבית המקבלת מטריצה ריבועית מלאה במספרים ,ומחזירה trueאם היא משופעת וfalse- אחרת. מטריצה משופעת: 4 2 1 0 7 5 0 -2 3 0 -1 -8 0 -6 -9 -3 מטריצה לא משופעת: 4 2 1 0 7 5 0 -2 3 6 -1 -8 0 -6 9 -3 דרך פעולה שלב :Iראשית נשים לב כי אם ניקח מטריצה משופעת ונחסיר ממנה את השורה הראשונה ואת העמודה הראשונה ,נקבל מטריצה משופעת. כעת נרצה לבדוק האם השורה והעמודה שהחסרנו מקיימות את התנאים של מטריצה משופעת. מהם תנאים אלו? כיצד ניתן לבצע זאת בצורה רקורסיבית? פתרון רקורסיבי: בהינתן מטריצה ריבועית dataואינדקס מסוים ( iשיהיה מספר של שורה/עמודה חוקית) ,נרצה לבדוק האם השורה והעמודה של תת המטריצה המתחילה במיקום ] [i][iמקיימות את התנאים. (הרעיון :נתחיל מאינדקס ] ,[i][iוכל פעם נתקדם תא אחד ימינה ותא אחד למטה ונבדוק האם הם מקיימים את התנאים .נעצור כאשר התנאים לא מתקיימים או כאשר סיימנו לעבור על כל תאי השורה והעמודה). Page 5 Practical session #6 הקוד public static boolean check(int[][] data, int index) { return (data[index][index] == 0 && check(data, index, 1)); } public static boolean check(int[][] data, int index, int diff) { boolean flag = false; if (index+diff == data.length) flag = true; else if (data[index][index+diff] <= 0 || data[index+diff][index] >= 0) flag = false; else flag = check(data, index, diff+1); return flag; } :)I פתרון איטרטיבי (לשלב // 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 diff = 1; (diff < data.length - index) && flag; diff = diff + 1) { if (data[index][index+diff] <= 0 || data[index+diff][index] >= 0) flag = false; } return flag; } . בדיקה האם המטריצה כולה משופעת:II שלב . כעת נצטרך לבדוק האם כל המטריצה היא משופעת. מצאנו האם שורה ועמודה הן חוקיותI בשלב בכל שלב של הרקורסיה נתייחס למטריצה קטנה יותר (נתקדם לאורך האלכסון) ונבדוק אם תת המטריצה היא .שיפועית וגם שאר התנאים עבור המטריצה הנוכחית מתקיימים ?מהו מקרה הבסיס? ומה נבצע עבורו . ולכן נבדוק אם מכילה אפס1x1 תת המטריצה בגודל:תשובה Practical session #6 Page 6 הקוד { )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 than it’s OK { )if (index == data.length - 1 { )if (data[index][index] == 0 ;isSlope = true } { else ;isSlope = false } } else ;))isSlope = (check(data,index) && slope(data, index+1 ;return isSlope } כפי שציינו בתרגולים ,הרעיון ברקורסיה הוא שאנו מניחים שהפונקציה יודעת לפתור בעיה קטנה יותר מהבעיה המקורית ומבצעים צעד למציאת הפתרון לבעיה הגדולה יותר. דוגמא :5המבוך המבוך" :דני הקטן הלך לאיבוד ,אנא עזרו לו למצוא את דרכו הביתה". נתון מערך דו-מימדי n x mשל שלמים ,המתאר מבוך: נקודת ההתחלה היא ) ,(0,0נקודת הסיום היא ) ,(n-1,m-1במצב ההתחלתי לכל תא ערך 0או ,1כאשר 1מסמל "דרך פנויה" ו 0-מסמל "דרך חסומה" (קיר) .בנוסף ,נסמן ב 3-את התאים שבהם כבר ביקרנו כדי שלא נחזור אליהם שנית וכן נסמן ב 7-את הדרך מההתחלה אל הסיום .הכיוונים המותרים הם למטה ,ימינה ,למעלה ושמאלה. Page 7 The Solution: The Maze: 7770110001111 3077707771001 0000707070300 7770777070333 7070000773003 7077777703333 7000000000000 7777777777777 1110110001111 1011101111001 0000101010100 1110111010111 1010000111001 1011111101111 1000000000000 1111111111111 Practical session #6 לפני שנממש את הפונקציה נתאר את אלגוריתם הפתרון הרקורסיבי .האלגוריתם נעזר בפונקציה ) valid(grid, row, colהמקבלת כקלט מבוך ,שורה וטור ומחזירה ערך בוליאני – trueאם התא במבוך המוגדר ע"י השורה והטור הוא תא חוקי (כלומר לא חורג מגבולות המבוך) ,פנוי ולא ביקרנו בו כבר (כלומר ,מסומן ב ,)1-ו- falseאחרת .קריאות לפונקציות מודגשות בקו. )solve(grid, row, col הגדר משתנה בוליאני בשם doneואתחל אותו לfalse- אם ( ),) valid(grid, row, col oסמן ב grid-את תא ] [row][colבמספר */ 3סימון שביקרנו בתא ואין צורך לבקר בו שוב */ oאם ( rowהיא השורה התחתונה ב grid-וגם colהוא הטור הימני ב */ ,) grid-תנאי עצירה */ שנה את ערכו של doneל */ true-הגענו ליציאה */ oאחרת, שנה את ערכו של 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)-נסה שמאלה */ oאם ( doneהינו ,) true סמן ב grid-את תא ] [row][colבמספר */ 7תא זה הוא חלק מהפתרון */ החזר את done הערה :המשימה שלנו היא למצוא מעבר מנקודת ההתחלה לנקודת הסיום ,אך נשים לב שהאלגוריתם למעלה אינו מתחיל דווקא בנקודת ההתחלה ,אלא ימצא את הדרך ליציאה מכל נקודה במבוך .הקריאה הראשונה לאלגוריתם תעביר כקלט את הכניסה בתור שורה 0וטור .0 הקוד { )public static boolean solve(int[][] grid, int row, int col ;boolean done = false { ))if (valid(grid, row, col ;grid[row][col] = 3 // cell has been tried )if (row == grid.length - 1 && col == grid[0].length - 1 done = true; // maze is solved { else done = solve(grid, row + 1, col); // try down )if (!done done = solve(grid, row, col + 1); // try right )if (!done done = solve(grid, row - 1, col); // try up )if (!done done = solve(grid, row, col - 1); // try left } )if (done grid[row][col] = 7; // part of the final path } ;return done } // function solve Page 8 Practical session #6 דוגמת הרצה ( +הסבר): לפני הקריאה הראשונה לפונקציה הרקורסיבית המבוך נראה כך: 1110110001111 1011101111001 0000101010100 1110111010111 1010000111001 1011111101111 1000000000000 1111111111111 בשלב כלשהו בריצה ,אנו נמצאים בקריאה אשר סימנה ב 3-את התא המודגש: 3330110001111 3033301111001 0000301010100 1110333010111 1010000111001 1011111101111 1000000000000 1111111111111 בשלב זה תהיה קריאה רקורסיבית על המשבצת מתחתיה ,משבצת בה המסומנת ב ,0-כלומר קיר. בדיקת ה valid-תחזיר falseואותה קריאה תסתיים. תתבצע קריאה נוספת מהמשבצת המסומנת ,הפעם ימינה ,ובדיקת ה valid-תיכשל שוב. בקריאה הבאה מהמשבצת המסומנת ,הפעם למעלה ,בדיקת ה valid-תחזיר .true המשבצת תסומן ב 3-והריצה תמשיך. 3330110001111 3033301111001 0000303010100 1110333010111 1010000111001 1011111101111 1000000000000 1111111111111 בסיום הריצה ,כשהגענו אל היציאה ,הדרך תסומן ב 7-והמבוך יראה כך: : Maze solved!: 7770110001111 3077707771001 0000707070300 7770777070333 7070000773003 7077777703333 7000000000000 7777777777777 דיון: שאלה :מה היה יכול לקרות אם לא היינו מסמנים כל תא שבדקנו (כאן סימון זה היה המספר ?)3 שאלה :האם תמיד יש פתרון אחד? אם לא ,איזה פתרון נמצא? Page 9 Practical session #6 :main- וvalid, print_maze כולל פונקציות,קוד המחלקה המלאה public class Maze { public static boolean solve(int[][] grid, int row, int col) { boolean done = false; if (valid(grid, row, col)) { grid[row][col] = 3; // cell has been tried if (row == grid.length - 1 && col == grid[0].length - 1) done = true; // maze is solved else { done = solve(grid, row + 1, col); //try down if (!done) done = solve(grid, row, col + 1);// try right if (!done) done = solve(grid, row - 1, col);// try up if (!done) done = solve(grid, row, col - 1);// try left } if (done) grid[row][col] = 7; // part of the final path } return done; } private static boolean valid(int[][] grid, int row, int col) { boolean result = false; // check if cell is in the bounds of the matrix if (row >= 0 && row < grid.length && col >= 0 && col < grid[0].length) // check if cell is not blocked and not previously tried if (grid[row][col] == 1) result = true; return result; } public static void print_maze(int[][] maze) { for (int i = 0; i < maze.length; i = i + 1) { for (int j = 0; j < maze[0].length; j = j + 1) System.out.print(maze[i][j]); System.out.println(); } System.out.println(); } Practical session #6 Page 10 { )public static void main(String[] args int[][] grid = {{1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1}, {1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1}, {0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0}, {1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1}, {1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1}, {1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, ;}}{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;)"System.out.println("The labyrinth:\n ;)print_maze(grid ;)boolean ans = solve(grid, 0, 0 { )if (ans ;)"System.out.println("Maze solved!:\n ;)print_maze(grid } else ;)"System.out.println("There is no solution } } דוגמא :6 כתבו פונקציה רקורסיבית שמקבלת מחרוזת ומחזירה את המחרוזת הפוכה. דוגמא :עבור הקלט ” “helloהפונקציה תחזיר את הפלט ”.“olleh דרך פעולה מקרה הבסיס: אם מדובר במחרוזת ריקה (ללא תווים) או במחרוזת בעלת תו אחד ניתן להחזיר את התו עצמו (ובמקרה של ריקה להחזיר ""( אחרת ,נמצא את המחרוזת ההפוכה של מחרוזת קטנה יותר (הקטנת הבעיה) ע"י צמצום המחרוזת בתו אחד השמאלי ,ונוסיף את התו השמאלי מימין למחרוזת ההפוכה שנקבל מהקריאה הרקורסיבית. הקוד {)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 ניתן להבחין שהפונקציה הזו אינה פונקצית זנב מכיוון שיש הוספת תו לאחר הקריאה הרקורסיבית. Page 11 Practical session #6