1 - המחלקה למדעי המחשב

‫תרגול ‪- 6‬‬
‫רקורסיות‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪1‬‬
‫רקורסיה‬
‫פונקציה רקורסיבית היא פונקציה שקוראת לעצמה‪.‬‬
‫פונקציה רקורסיבית מחושבת כמו כל פונקציה אחרת (העברת פרמטרים‪,‬‬
‫משתנים לוקאליים‪ ,‬תחום הכרה של המשתנים וכו')‪.‬‬
‫מוטיבציה‪ :‬ישנן בעיות רבות שעבורן‬
‫פתרון רקורסיבי פשוט יותר מפתרון איטרטיבי‪.‬‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪2‬‬
‫רקורסיה‬
‫דוגמה ‪ :1‬סכום מספרים טבעיים עד ‪n‬‬
‫אפשר בלולאה (פתרון איטרטיבי)‪:‬‬
‫{ )‪public static int sum(int n‬‬
‫;‪int ans = 0‬‬
‫{ )‪for (int i = 1; i <= n; i = i + 1‬‬
‫;‪ans = ans + i‬‬
‫}‬
‫;‪return ans‬‬
‫}‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪3‬‬
‫רקורסיה‬
‫אפשר גם בדרך אחרת‪ :‬נניח שיש לנו פונקציה אחרת בשם ‪ magic‬שמחזירה‬
‫את הסכום )‪.1 + 2+ ... + (n-1‬‬
‫אז ‪ sum‬יכולה להראות כך‪:‬‬
‫{ )‪public static int sum(int n‬‬
‫;‪int ans = magic(n)+ n‬‬
‫;‪return ans‬‬
‫}‬
‫אבל )‪ magic(n‬מחזירה בדיוק מה ש‪ sum(n-1)-‬הייתה מחזירה‪.‬‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪4‬‬
‫רקורסיה‬
‫ניתן להגדיר נוסחת נסיגה עבור החישוב‪sum(n) = sum(n-1)+n :‬‬
‫…‬
‫;‪int ans = sum(n-1)+ n‬‬
‫…‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪5‬‬
‫רקורסיה‬
‫התוצאה היא פונקציה אחת שקוראת לעצמה‪:‬‬
‫{ )‪sum(int n‬‬
‫‪// stop condition‬‬
‫;‪1‬‬
‫;‪sum(n - 1) + n‬‬
‫‪public static int‬‬
‫;‪int ans‬‬
‫)‪if (n == 1‬‬
‫= ‪ans‬‬
‫‪else‬‬
‫= ‪ans‬‬
‫;‪return ans‬‬
‫}‬
‫*** מעקב על דוגמת הרצה וציור טבלאות מעקב משתנים‪.‬‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪6‬‬
‫רקורסיה‬
‫שלושת הכללים לבניית פונקציה רקורסיבית‬
‫‪ .1‬תנאי עצירה שניתן לענות עליו ללא קריאה רקורסיבית‪.‬‬
‫אם לא נשים תנאי עצירה התוכנית עלולה להיכנס ללולאה אינסופית‪.‬‬
‫‪ .2‬קריאה רקורסיבית עם קלט הקרוב יותר לתנאי העצירה ("הקטנת‬
‫הבעיה")‪.‬‬
‫אם לא מקטינים את הבעיה לא נגיע לתנאי העצירה‪ ,‬כלומר שוב תהיה לולאה‬
‫אינסופית‪.‬‬
‫‪ .3‬שימוש בתוצאת הקריאה הרקורסיבית לחישוב התוצאה המוחזרת (הנחת‬
‫האינדוקציה)‪.‬‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪7‬‬
‫דוגמה ‪ – 2‬משולש פסקל‬
‫תזכורת‪ :‬משולש פסקל הוא סידור של מספרים בצורת משולש‪ ,‬הנבנה באופן הבא‪:‬‬
‫הקודקוד העליון של משולש זה מכיל את המספר ‪ ,1‬וכל מספר במשולש מהווה‬
‫את סכום שני המספרים שנמצאים מעליו (המספרים שנמצאים על שוקי‬
‫המשולש הם כולם ‪.)1‬‬
‫‪n‬‬
‫‪1‬‬
‫‪0‬‬
‫‪1 1‬‬
‫‪1‬‬
‫‪1 2 1‬‬
‫‪2‬‬
‫‪ ,‬נותן את ‪ n‬בשורה ה‪m -‬המספר ה‪-‬‬
‫‪1 3 3 1‬‬
‫‪3‬‬
‫התשובה לשאלה "בכמה דרכים‬
‫מתוך ‪1 4 6‬‬
‫‪ 4‬עצמים‪4 1‬‬
‫‪m‬שונות אפשר לבחור‬
‫‪1 5 ( "10‬‬
‫‪10 5 1‬‬
‫‪5‬‬
‫מקדם בינומי)‪n .‬‬
‫עצמים?‬
‫‪0 1 2 3 4 5 m‬‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪8‬‬
‫דוגמה ‪ – 2‬משולש פסקל‬
‫נכתוב פונקציה רקורסיבית )‪ pascal(int n, int m‬שתחשב את המספר המופיע‬
‫בשורה ‪ n‬ובעמודה ‪ m‬במשולש פסקל‪.‬‬
‫תאור האלגוריתם‬
‫• תנאי עצירה‪ :‬אם ‪ m‬הוא ‪ 0‬נחזיר ערך ‪ .1‬אם ‪ n=m‬נחזיר ערך ‪.1‬‬
‫• חוקיות הקלט‪ n :‬ו‪ m -‬הם שלמים אי‪-‬שליליים‪ .‬אם ‪ m>n‬נציין שיש‬
‫שגיאה בקלט‪.‬‬
‫• קריאות רקורסיביות על קלט קטן יותר‪:‬‬
‫קריאה אחת עם ‪ n-1‬ו‪( m -‬המספר מעליו)‬
‫קריאה שנייה עם ‪ n-1‬ו ‪( m-1‬המספר מעל ומשמאל)‬
‫• שילוב התוצאות לקבלת תשובה‪ :‬החזרת סכום של הערכים‬
‫שהתקבלו משתי הקריאות הרקורסיביות‪.‬‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫‪9‬‬
‫ – משולש פסקל‬2 ‫דוגמה‬
// Pascal number in row n and column m.
public static int pascal(int n, int m){
int ans;
if (m<0 || n<0 || m>n) {
ans = -1;
} else if (m==0 || n == m) {
ans = 1;
} else {
ans = pascal(n-1,m) + pascal(n-1,m-1);
}
return ans;
10
‫ בן גוריון‬,‫מבוא למדעי המחשב‬
}
‫דוגמה ‪ -3‬זוגיים ואי זוגיים‬
‫המטרה‪ :‬רוצים לבדוק האם מספר טבעי ‪ n‬הוא זוגי או אי‪-‬זוגי באמצעות הפונקציות ‪ even‬ו‪-‬‬
‫‪( odd‬ללא פעולות חלוקה ושארית)‪.‬‬
‫{ )‪public static boolean even(int n‬‬
‫;‪boolean ans‬‬
‫)‪if (n == 0‬‬
‫;‪ans = true‬‬
‫‪else‬‬
‫;)‪ans = odd(n - 1‬‬
‫;‪return ans‬‬
‫}‬
‫{ )‪public static boolean odd(int n‬‬
‫;‪boolean ans‬‬
‫)‪if (n == 1‬‬
‫מה קורה כאשר‬
‫;‪ans = true‬‬
‫‪even‬מפעילים את‬
‫‪else‬‬
‫על מספר אי‪-‬זוגי‬
‫;)‪ans = even(n - 1‬‬
‫גדול מ ‪?0‬‬
‫;‪return ans‬‬
‫מבוא למדעי המחשב‪ ,‬בן גוריון‬
‫}‬
‫‪11‬‬
‫ זוגיים ואי זוגיים‬-3 ‫דוגמה‬
:‫נסיון שני‬
public static boolean even(int n) {
boolean ans;
if (n == 0)
ans = true;
else
ans = odd(n - 1);
return ans;
}
public static boolean odd(int n) {
boolean ans;
if (n == 0)
ans = false;
else
ans = even(n - 1);
return ans;
}
12
‫ בן גוריון‬,‫מבוא למדעי המחשב‬
:‫רקורסיה הדדית‬
even ‫קוראת לעצמה‬
‫דרך‬odd -‫ ו‬,odd
‫קוראת לעצמה דרך‬
even .
‫דוגמא ‪1‬‬
‫נכתוב פונקציה רקורסיבית שמציירת משולש הפוך‬
‫בגובה של ‪ n‬שורות‪.‬‬
‫לדוגמא‪ ,‬עבור ‪ n=7‬נקבל את המשולש‪:‬‬
‫דרך פעולה‪:‬‬
‫נדפיס שורת כוכביות באורך ‪ ,n‬ואז נדפיס משולש בגובה ‪.n-1‬‬
‫תנאי העצירה יהיה כאשר נגיע להדפיס משולש בגובה ‪.0‬‬
‫*******‬
‫******‬
‫*****‬
‫****‬
‫***‬
‫**‬
‫*‬
‫‪13‬‬
:‫והפתרון‬
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);
}
}
14
***
**
*
:n=3 ‫עבור המקרה בו קוראים לפונקציה עם‬
!‫כן‬
?‫רקורסית זנב‬
??‫מה היה קורה אם היינו רוצים להדפיס משולש ישר‬
,n-1 ‫ עם‬drawTriangle -‫היינו צריכים קודם לקרוא ל‬
: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();
}
}
15
:‫אם נשלב את שני החלקים‬
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();
}
}
16
:‫נקבל‬
*****
****
***
**
*
*
**
***
****
*****
‫דוגמא ‪2‬‬
‫כתבו פונקציה רקורסיבית שמקבלת מחרוזת‬
‫ומחזירה ‪ true‬אם היא פָּ ִלינְ ְדרֹום ו‪ false -‬אחרת‪.‬‬
‫תזכורת‪ :‬פָּ ִלינְ ְדרֹום היא מחרוזת שניתן לקרוא משני הכיוונים‪ ,‬משמאל לימין ומימין לשמאל‪,‬‬
‫ולקבל אותה תוצאה‪.‬‬
‫לדוגמא‪ :‬המחרוזות "ארון קיר היה ריק נורא"‪" ,‬ילד כותב בתוך דלי" ו‪ madam -‬הן‬
‫פָּ ִלינְ ְדרֹום‪ ,‬לעומת זאת המחרוזת ‪ hello‬אינה פָּ ִלינְ ְדרֹום‪.‬‬
‫הנחת יסוד‪ :‬מחרוזת ריקה (ללא תווים) ומחרוזת בעלת תו אחד הן פָ ִלינְ ְדרֹום‪.‬‬
‫‪17‬‬
‫דוגמא ‪ – 2‬דרך פעולה‬
‫דרך פעולה‪:‬‬
‫מקרה הבסיס‪:‬‬
‫אם מדובר במחרוזת ריקה (ללא תווים) או במחרוזת בעלת תו אחד ניתן להחזיר ‪.true‬‬
‫אחרת‪ ,‬נבדוק עבור מחרוזת קטנה יותר (הקטנת הבעיה) ע"י צמצום המחרוזת בתו אחד‬
‫מכל צד‪.‬‬
‫אם הפעלת הפונקציה הרקורסיבית על המחרוזת המוקטנת תחזיר ‪ true‬וגם שני התווים‬
‫הקיצוניים שהורדנו מהמחרוזת המקורית שווים נחזיר ‪ ,true‬אחרת נחזיר ‪.false‬‬
‫‪18‬‬
:‫והפתרון‬
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;
}
19
‫בעיית ‪)SUSU( Subset Sum‬‬
‫• בהנתן מערך של משקולות ומשקל נוסף‪ ,‬נרצה לבדוק האם ניתן‬
‫להרכיב מהמשקולות משקל השווה למשקל הנתון‪.‬‬
‫• דוגמא לקלט‪:‬‬
‫}‪weights={1,7,9,3‬‬
‫‪Sum = 12‬‬
‫• במקרה זה הפונקציה תחזיר ‪ true‬כי ניתן לחבר את המשקולות ‪ 9‬ו‬
‫‪ 3‬ולקבל את הסכום ‪.12‬‬
‫• מה יוחזר עבור ‪?sum=15‬‬
‫• תשובה‪false :‬‬
‫‪20‬‬
‫אסטרטגיית הפתרון‬
‫נעבור על כל האיברים במערך ונבחן אפשרויות בהן איבר נבחר או לא‬
‫נבחר‪.‬‬
‫נתבונן באיבר הראשון במערך‪ .‬ייתכן שהוא ייבחר לקבוצת המשקולות‬
‫שתרכיב את ‪ sum‬ויתכן שלא‪.‬‬
‫• אם הוא לא ייבחר – אזי נותר לפתור בעיה קטנה יותר והיא‪ :‬האם‬
‫ניתן להרכיב את הסכום ‪ sum‬מבין המשקולות שבתאים‬
‫]‪weights[1,…,n-1‬‬
‫• אם הוא ייבחר – אזי נותר לפתור בעיה קטנה יותר והיא‪ :‬האם ניתן‬
‫להרכיב את הסכום ]‪ sum-weight[0‬מבין המשקולות שבתאים‬
‫]‪.weights[1,…, n-1‬‬
‫כנ"ל לגבי יתר האיברים בצורה רקורסיבית‪.‬‬
‫‪21‬‬
‫אסטרטגיית הפתרון – המשך‬
‫נעבור על כל האיברים במערך ונבחן אפשרויות בהן איבר נבחר או לא‬
‫נבחר‪.‬‬
‫קיימים שני מקרי בסיס‪:‬‬
‫‪ .1‬הגענו לסכום הדרוש או במילים אחרות הסכום הנותר הינו אפס‪.‬‬
‫‪ .2‬הגענו לסוף המערך – עברנו על כל האיברים ולא מצאנו צירוף של‬
‫איברים שסכומם שווה לסכום הנדרש‪.‬‬
‫‪22‬‬
‫פתרון‬
‫פתרון זה קל להציג כפונקציה רקורסיבית‪:‬‬
‫)‪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‬‬
‫}‬
‫‪23‬‬
)‫פתרון (המשך‬
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;
}
24
‫דוגמא ‪4‬‬
‫מטריצה תקרא משופעת אם‪:‬‬
‫‪.1‬כל איברי האלכסון הראשי שווים לאפס‪.‬‬
‫‪.2‬כל האיברים שנמצאים מתחת לאלכסון הראשי הם שליליים‪.‬‬
‫‪.3‬כל האיברים שנמצאים מעל לאלכסון הראשי הם חיוביים‪.‬‬
‫(מניחים כי המימד הראשי מגדיר את השורות והמשני מגדיר את העמודות)‪.‬‬
‫מטריצה משופעת‪:‬‬
‫‪2‬‬
‫‪7‬‬
‫‪4‬‬
‫‪5‬‬
‫‪0 1‬‬
‫‪-2 0‬‬
‫‪-8 -1 0 3‬‬
‫‪-3 -9 -6 0‬‬
‫מטריצה לא משופעת‪:‬‬
‫‪2‬‬
‫‪4‬‬
‫‪1‬‬
‫‪0‬‬
‫‪7‬‬
‫‪5‬‬
‫‪0‬‬
‫‪-2‬‬
‫‪3‬‬
‫‪6‬‬
‫‪-8 -1‬‬
‫‪0‬‬
‫‪-6‬‬
‫‪-3‬‬
‫‪9‬‬
‫‪25‬‬
‫דוגמא ‪ – 4‬הרעיון‬
‫נשים לב שאם נחסיר ממטריצה משופעת את השורה‬
‫הראשונה ואת העמודה הראשונה‪ ,‬נקבל מטריצה משופעת‪.‬‬
‫‪0‬‬
‫‪4‬‬
‫‪1‬‬
‫‪5‬‬
‫‪0‬‬
‫‪-2 0‬‬
‫‪-8 -1‬‬
‫‪1‬‬
‫‪4‬‬
‫‪5‬‬
‫‪0‬‬
‫‪0‬‬
‫‪0‬‬
‫‪-1‬‬
‫‪0‬‬
‫‪-2‬‬
‫‪-8‬‬
‫נשאר לבדוק האם השורה והעמודה שהחסרנו מקיימות את‬
‫התנאים‪.‬‬
‫‪26‬‬
‫דוגמא ‪– 4‬המשך‬
‫נניח שקיימת פונקציה ‪ check‬המקבלת מטריצה ריבועית ואינדקס‬
‫של שורה‪/‬עמודה ומחזירה האם השורה והעמודה מקיימות את‬
‫התנאים‪.‬‬
‫כתבו פונקציה רקורסיבית המקבלת מטריצה מלאה במספרים‪,‬‬
‫ומחזירה ‪ true‬אם היא משופעת ו‪ false-‬אחרת‪:‬‬
‫][][‪public static boolean slope(int‬‬
‫;)‪data‬‬
‫‪27‬‬
‫דוגמא ‪– 4‬המשך‬
‫דרך פעולה‪:‬‬
‫בכל שלב של הרקורסיה נתייחס למטריצה הקטנה יותר (נתקדם לאורך האלכסון) ונבדוק אם תת‬
‫המטריצה משופעת וגם שאר התנאים עבור המטריצה הנוכחית מתקיימים ‪.‬‬
‫‪4‬‬
‫‪5‬‬
‫‪0 1‬‬
‫‪-2 0‬‬
‫‪0‬‬
‫‪-8 -1‬‬
‫‪5‬‬
‫‪0‬‬
‫‪0‬‬
‫‪-1‬‬
‫‪0‬‬
‫מהו מקרה הבסיס? ומה נבצע עבורו?‬
‫תת המערך בגודל ‪ 1x1‬ולכן נבדוק אם מכיל אפס‪.‬‬
‫‪28‬‬
:‫והפתרון‬
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;
}
29
‫ –המשך‬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;
}
30
‫דוגמה ‪ - 5‬המבוך‬
‫‪31‬‬
‫המבוך‬
‫• נתון מערך דו‪-‬מימדי ‪ n x m‬של שלמים‪ ,‬בשם ‪,grid‬‬
‫המתאר מבוך‪:‬‬
‫• נקודת ההתחלה היא התא במיקום )‪ (0,0‬במערך‪,‬‬
‫נקודת הסיום היא התא במיקום )‪,(n-1,m-1‬‬
‫במצב ההתחלתי לכל תא ערך ‪ 0‬או ‪,1‬‬
‫כאשר ‪ 1‬מסמל "דרך פנויה" ו‪ 0-‬מסמל "דרך חסומה"‬
‫(קיר)‪.‬‬
‫• הפיתרון הרצוי הוא מסלול רציף מנקודת ההתחלה‬
‫לנקודת הסיום (מותר ללכת למטה‪ ,‬ימינה‪ ,‬למעלה‬
‫ושמאלה)‪ .‬את המסלול נסמן בעזרת החלפת ה‪-1 -‬ים‬
‫שעל המסלול‪ ,‬ב‪.7 -‬‬
‫‪32‬‬
‫המבוך ‪ -‬דוגמא‬
‫• מבוכים‪:‬‬
‫‪1110‬‬
‫‪1011‬‬
‫‪0001‬‬
‫‪1101‬‬
‫‪1110‬‬
‫‪1010‬‬
‫‪1110‬‬
‫‪0001‬‬
‫מבוך ללא פתרון‬
‫מה יכול להתרחש אם נטייל על המסלול‪ ,‬ולא‬
‫נבדוק האם ביקרנו כבר בתא אליו אנו הולכים?‬
‫• בפיתרון‪ :‬נסמן ב‪ -‬ערך ‪ 3‬את התאים שבהם כבר ביקרנו‬
‫כדי שלא נחזור אליהם שנית‪.‬‬
‫נסמן ב‪ 7-‬את הדרך מההתחלה אל הסיום‪.‬‬
‫‪33‬‬
‫המבוך‬
‫• בפיתרון‪ :‬נסמן ב‪ 3-‬את התאים שבהם כבר ביקרנו כדי‬
‫שלא נחזור אליהם שנית‪.‬‬
‫נסמן ב‪ 7-‬את הדרך מההתחלה אל הסיום‪.‬‬
‫• מבוך‪:‬‬
‫‪1110110001111‬‬
‫‪1011101111001‬‬
‫‪0000101010100‬‬
‫‪1110111010111‬‬
‫‪1010000111001‬‬
‫‪1011111101111‬‬
‫‪1000000000000‬‬
‫‪1111111111111‬‬
‫‪34‬‬
‫המבוך‬
‫פתרון רצוי‪:‬‬
‫‪7770110001111‬‬
‫‪3077707771001‬‬
‫‪0000707070300‬‬
‫‪7770777070333‬‬
‫‪7070000773003‬‬
‫‪7077777703333‬‬
‫‪7000000000000‬‬
‫‪7777777777777‬‬
‫‪35‬‬
‫איך חושבים על זה בצורה רקורסיבית?‬
‫• ננסה להסתכל מהתא הנוכחי על התאים השכנים ולראות‬
‫אם יש דרך מאחד מהן‪.‬‬
‫• אם כן – הדרך מהתא הנוכחי היא הדרך המתקבלת ע"י‬
‫הוספת התא הנוכחי לתחילת הדרך שבה ניתן להגיע‬
‫מהתא השכן‪.‬‬
‫• המשימה שלנו היא למצוא מעבר מנקודת ההתחלה‬
‫לנקודת הסיום‪ ,‬אך האלגוריתם שנתאר אינו מתחיל דווקא‬
‫בנקודת ההתחלה‪ ,‬אלא ימצא את הדרך ליציאה מכל‬
‫נקודה במבוך‪.‬‬
‫)‪solve(int[][] grid, int row, int col‬‬
‫‪36‬‬
‫תיאור האלגוריתם‬
‫• הקריאה הראשונה לאלגוריתם תעביר כקלט את‬
‫הכניסה בתור שורה ‪ 0‬וטור ‪.0‬‬
‫)‪solve(grid, 0, 0‬‬
‫• האלגוריתם נעזר בפונקציה‬
‫)‪valid(grid, row, col‬‬
‫המקבלת כקלט מבוך‪ ,‬שורה וטור‪,‬‬
‫ומחזירה ערך בוליאני – ‪ true‬אםם התא במבוך‬
‫המוגדר ע"י השורה והטור הוא תא חוקי‪.‬‬
‫• תא חוקי = לא חורג מגבולות המבוך‪ ,‬פנוי ולא‬
‫ביקרנו בו כבר (כלומר‪ ,‬מסומן ב‪.)1-‬‬
‫‪37‬‬
‫תיאור )‪solve(grid, row, col‬‬
‫‪ .1‬הגדר משתנה בוליאני‪.‬‬
‫‪ .2‬אם ( )‪) valid(grid, row, col‬‬
‫‪ .2.1‬סימון שביקרנו בתא ואין צורך לבקר בו שוב‪.‬‬
‫‪ .2.2‬בדיקת תנאי עצירה‪.‬‬
‫‪ .2.3‬אחרת‪,‬‬
‫‪ .2.3.1‬נסה למטה‪.‬‬
‫‪ .2.3.2‬נסה ימינה‪.‬‬
‫‪ .2.3.3‬נסה למעלה‪.‬‬
‫‪ .2.3.4‬נסה שמאלה‪.‬‬
‫‪ .2.4‬אם ‪ done‬הינו ‪ true‬סמן שתא זה הוא חלק מהפתרון‪.‬‬
‫‪ .3‬החזר את ‪.done‬‬
‫‪38‬‬
‫תיאור )‪solve(grid, row, col‬‬
‫‪/* done=false */‬‬
‫‪ .1‬הגדר משתנה בוליאני‪.‬‬
‫‪ .2‬אם ( )‪) valid(grid, row, col‬‬
‫‪ .2.1‬סימון שביקרנו בתא ואין צורך לבקר בו שוב‪.‬‬
‫‪/* grid[row][col]=3 */‬‬
‫‪ .2.2‬בדיקת תנאי עצירה‪.‬‬
‫‪ */‬אם ‪ row‬היא השורה התחתונה וגם ‪ col‬הוא הטור הימיני‬
‫ב‪ ,grid -‬אז ‪/*done=true‬‬
‫‪ .2.3‬אחרת‪,‬‬
‫‪/*done=solve(grid, row+1, col) */‬‬
‫‪ .2.3.1‬נסה למטה‪.‬‬
‫‪/*done=solve(grid, row, col+1) */‬‬
‫‪ .2.3.2‬נסה ימינה‪.‬‬
‫‪/*done=solve(grid, row-1, col) */‬‬
‫‪ .2.3.3‬נסה למעלה‪.‬‬
‫‪ .2.3.4‬נסה שמאלה‪/*done=solve(grid, row, col-1) */ .‬‬
‫‪ .2.4‬אם ‪ done‬הינו ‪ true‬סמן שתא זה הוא חלק מהפתרון‪.‬‬
‫‪/*grid[row][col]=7 */‬‬
‫‪39‬‬
‫‪ .3‬החזר את ‪.done‬‬
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;
}
40
‫דוגמת הרצה‬
‫•‬
‫נק' התחלה‬
‫נסה למטה‬
‫נסה למטה‬
‫נסה ימינה‬
‫נסה למעלה‬
‫נסה שמאלה‬
‫חזור צעד אחד אחורה‬
‫נסה ימינה‬
‫‪...‬‬
‫‪33‬‬
‫‪1110110001111‬‬
‫‪3‬‬
‫‪1011101111001‬‬
‫‪0000101010100‬‬
‫‪1110111010111‬‬
‫‪1010000111001‬‬
‫‪1011111101111‬‬
‫‪1000000000000‬‬
‫‪1111111111111‬‬
‫‪41‬‬
‫• בשלב כלשהו בריצה‪ ,‬אנו נמצאים בקריאה אשר סימנה‬
‫ב‪ 3-‬את התא המודגש‪)row=3, col=6( :‬‬
‫‪3330110001111‬‬
‫‪3033301111001‬‬
‫‪0000301010100‬‬
‫‪1110333010111‬‬
‫‪1010000111001‬‬
‫‪1011111101111‬‬
‫‪1000000000000‬‬
‫‪1111111111111‬‬
‫‪42‬‬
‫• בשלב כלשהו בריצה‪ ,‬אנו נמצאים בקריאה אשר סימנה‬
‫ב‪ 3-‬את התא המודגש‪)row=3, col=6( :‬‬
‫‪3330110001111‬‬
‫‪3033301111001‬‬
‫‪0000301010100‬‬
‫‪1110333010111‬‬
‫‪1010000111001‬‬
‫‪1011111101111‬‬
‫‪1000000000000‬‬
‫‪1111111111111‬‬
‫• בשלב זה תהיה קריאה רקורסיבית על המשבצת‬
‫מתחתיה‪ ,‬משבצת המסומנת ב‪ ,0-‬כלומר קיר‪ .‬בדיקת ה‪-‬‬
‫‪ valid‬תחזיר ‪ false‬ואותה קריאה תסתיים‪.‬‬
‫‪43‬‬
‫• תתבצע קריאה נוספת מהמשבצת המסומנת‪ ,‬הפעם‬
‫ימינה‪ ,‬ובדיקת ה‪ valid-‬תיכשל שוב‪.‬‬
‫‪3330110001111‬‬
‫‪3033301111001‬‬
‫‪0000301010100‬‬
‫‪1110333010111‬‬
‫‪1010000111001‬‬
‫‪1011111101111‬‬
‫‪1000000000000‬‬
‫‪1111111111111‬‬
‫‪44‬‬
‫• בקריאה הבאה מהמשבצת המסומנת‪ ,‬הפעם למעלה‪,‬‬
‫בדיקת ה‪ valid-‬תחזיר ‪true‬‬
‫‪3330110001111‬‬
‫‪3033301111001‬‬
‫‪0000301010100‬‬
‫‪1110333010111‬‬
‫‪1010000111001‬‬
‫‪1011111101111‬‬
‫‪1000000000000‬‬
‫‪1111111111111‬‬
‫‪45‬‬
‫• הקריאה הרקורסיבית במשבצת העליונה תסמנה ב‪3-‬‬
‫והריצה תמשיך‪.‬‬
‫‪3330110001111‬‬
‫‪3033301111001‬‬
‫‪0000303010100‬‬
‫‪1110333010111‬‬
‫‪1010000111001‬‬
‫‪1011111101111‬‬
‫‪1000000000000‬‬
‫‪1111111111111‬‬
‫‪46‬‬
‫• זו התמונה אחרי כמה קריאות‪ .‬בשלב זה תתבצע קריאה‬
‫מהמשבצת המסומנת והיא נכשלת‪ .‬הקריאות חוזרות‪.‬‬
‫נכשלנו‪ :‬נחזור חזרה בכל‬
‫קריאה ונחזיר ‪false‬‬
‫‪3330110001111‬‬
‫‪3033303331001‬‬
‫‪0000303030300‬‬
‫‪1110333030333‬‬
‫‪3‬‬
‫‪1010000133003‬‬
‫‪1011111103333‬‬
‫‪1000000000000‬‬
‫‪1111111111111‬‬
‫• הקריאה הבאה מתבצעת למשבצת משמאל עם הערך ‪.1‬‬
‫‪47‬‬
‫• בתום הקריאה הרקורסיבית במשבצת המסומנת במרובע‬
‫ה – ‪ grid‬יראה כך‪ .‬הקריאה הרקורסיבית במשבצת זו‬
‫תחזיר ערך ‪.true‬‬
‫‪3330110001111‬‬
‫‪7‬‬
‫‪3033303331001‬‬
‫‪0000303070300‬‬
‫‪7770333070333‬‬
‫‪7070000773003‬‬
‫‪7077777703333‬‬
‫‪7000000000000‬‬
‫‪7777777777777‬‬
‫• במסגרת הקוראת (הקריאה במשבצת המסומנת באדום)‬
‫תסמן גם ‪ 7‬ותחזיר ‪ .true‬המשבצת הימנית לא תיבדק‪.‬‬
‫‪48‬‬
‫• בסיום הריצה‪ ,‬כשהגענו אל היציאה‪ ,‬הדרך תסומן ב‪7-‬‬
‫והמבוך יראה כך‪:‬‬
‫דיון‪:‬‬
‫‪‬האם תמיד יש פתרון אחד?‬
‫אם לא‪ ,‬איזה פתרון נמצא?‬
‫‪‬מה היה יכול לקרות אם לא‬
‫היינו מסמנים כל תא שבדקנו‬
‫(כאן סימון זה היה המספר ‪?)3‬‬
‫‪7770110001111‬‬
‫‪3077707771001‬‬
‫‪0000707070300‬‬
‫‪7770777070333‬‬
‫‪7070000773003‬‬
‫‪7077777703333‬‬
‫‪7000000000000‬‬
‫‪7777777777777‬‬
‫• ונחזיר ‪true‬‬
‫‪49‬‬
‫דוגמא ‪6‬‬
‫כתבו פונקציה רקורסיבית שמקבלת מחרוזת‬
‫ומחזירה את המחרוזת הפוכה‪.‬‬
‫לדוגמא‪ :‬עבור הקלט ”‪ “hello‬הפונקציה תחזיר את הפלט ”‪.“olleh‬‬
‫דרך פעולה‪:‬‬
‫מקרה הבסיס‪ :‬אם מדובר במחרוזת ריקה (ללא תווים) או במחרוזת בעלת‬
‫תו אחד ניתן להחזיר את התו עצמו (ובמקרה של ריקה להחזיר ""(‬
‫אחרת‪ ,‬נמצא את המחרוזת ההפוכה של מחרוזת קטנה יותר (הקטנת‬
‫הבעיה) ע"י צמצום המחרוזת בתו אחד השמאלי‪ ,‬ונוסיף את התו השמאלי‬
‫מימין למחרוזת ההפוכה שנקבל מהקריאה הרקורסיבית‪.‬‬
‫‪50‬‬
:‫והפתרון‬
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;
}
.i ‫ ממקום‬s ‫ מחזירה את המחרוזת‬s.substring(i) ‫ הפונקציה‬:‫שימו לב‬
51
)‫לא! (בכיתה ראינו דוגמא למימוש עם רקורסיית זנב‬
?‫רקורסית זנב‬