תרגול מספר 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
© Copyright 2025