You are on page 1of 31

‫ ‪ 83‬‬

‫פרק ‪4‬‬
‫ֶרקורסיה‬
‫כאשר אנו ניגשי לפתור בעיה‪ ,‬עומדי לרשותנו כמה כלי בסיסיי המקלי עלינו להגיע‬
‫לפתרו‪ .‬אחד הכלי לפתרו בעיה הוא שיטת " ַה ְפ ֵרד ! ְמ"‪#‬ל"‪ ,‬כלומר חלוקת הבעיה לתתבעיות‪.‬‬
‫כ‪ $‬פתרו כל אחת מהבעיות בנפרד מביא אותנו בסופו של דבר לפתרו הבעיה המקורית‪ ,‬על ידי‬
‫הרכבת כל הפתרונות של התתבעיות‪ .‬גישת "הפרד ומשול" משמשת אותנו ג בחיי היומיו‪.‬‬
‫לדוגמה‪ ,‬כאשר אנו מתכנני לצאת לטיול‪ ,‬אנו מפרקי את המשימה הגדולה "יציאה לטיול"‪,‬‬
‫לתתמשימות‪ :‬קביעת התארי‪ $‬והיעד‪ ,‬סוג הבגדי שיש לקחת‪ ,‬כמויות האוכל והשתייה הנחוצי‬
‫וכדומה‪ .‬ע פתרו כל המשימות הללו יש לקוות שנמצא את עצמנו בפתח הדלת‪ ,‬תרמיל על הגב‬
‫ומקל ביד‪...‬‬

‫לפעמי אנו נתקלי בבעיות שנית לפרק לתתבעיות שה למעשה זהות לבעיה המקורית‪ ,‬אלא‬
‫שה "קטנות יותר"‪ .‬פתרו תתבעיות אלה יאפשר לנו לפתור את הבעיה המקורית‪ .‬תתבעיה נית‬
‫לפתור באופ ישיר‪ ,‬א היא מספיק פשוטה‪ .‬א לא‪ ,‬נית לפתור אותה באותה שיטה‪ ,‬של פירוק‬
‫לבעיות פשוטות יותר מאותו סוג‪ .‬להל דוגמה מחיי היומיו‪ :‬כשעלינו לאסו( את ארבעת האחי‬
‫הקטני מהג ומבית הספר בצהריי‪ ,‬נית לחשוב על הפתרו של 'איסו( האח הקט' מהג‬
‫ו'איסו( שלושת האחרי' מבית הספר היסודי‪ .‬איסו( האח הקט הוא פעולה אחת‪ .‬איסו(‬
‫השלישייה מתפרט לכמה פעולות‪' :‬איסו( האח הקט בשלישייה'‪' ,‬איסו( שני האחרי הגדולי'‬
‫וכ‪ $‬הלאה‪ .‬פתרו הבעיה באמצעות פירוקה לכמה בעיות זהות קטנות יותר נקרא פתרו‬
‫רקורסיבי‪ .‬בפרק זה נלמד כיצד לפתור בעיות בשיטה רקורסיבית‪.‬‬

‫לפני שנתעמק ברקורסיות מעול התכנות‪ ,‬נבח בעיה נוספת )מתוקה( מהחיי‪...‬‬

‫חפיסת השוקולד‬
‫מחנכת הכיתה החליטה להפתיע את ‪ 32‬תלמידיה ולכבד בטבלת שוקולד שבה ‪ 32‬קוביות‪ .‬מכיוו‬
‫שיצא ל‪ $‬ש של תלמיד אחראי ביותר‪ ,‬היא מורה ל‪ $‬לקחת את הטבלה‪ ,‬ולחלקה כ‪ $‬שכל אחד‬
‫מהתלמידי ייהנה מקוביית שוקולד אחת‪ .‬זהו יו קי‪ -‬ח ויש סיבה לדאגה‪ :‬א תחלק את‬
‫הקוביות אחת אחת‪ ,‬תגלה עד מהרה שהחפיסה נמסה ורק בני מזל מעטי ייהנו מהשוקולד‪ ,‬שכ‬
‫רובו יישאר על אצבעותי‪ .$‬כיצד תפתור את הבעיה?‬
‫לאחר כמה שניות של מחשבה מאומצת‪ ,‬אתה מציע את הרעיו הבא‪:‬‬

‫אחלק את טבלת השוקולד לשני חלקי שווי‪ ,‬ואת כל חלק לתלמיד אחר‪ .‬כל תלמיד שבידו‬
‫מחצית הטבלה‪ ,‬ימשי‪ $‬באותה השיטה‪ ,‬כלומר יחלק את החפיסה שקיבל לשתיי‪ ,‬ויית כל‬
‫מחצית לתלמיד אחר‪ .‬כ‪ $‬יימש‪ $‬התהלי‪ $‬עד שבידי כל תלמיד תהיה קובייה אחת בלבד‪ .‬במקרה‬
‫זה – יאכל התלמיד את הקובייה הבודדה שבידו‪.‬‬

‫קל לראות כי בדר‪ $‬זו כל תלמיד מקבל קובייה אחת‪ ,‬ומה שיותר חשוב‪ ,‬החבילה מתחלקת‬
‫במהירות‪ .‬כל ילד בכיתה מקבל קוביית שוקולד מוצקה בתו‪ 5 $‬שלבי בלבד‪ ,‬במקו שוקולד נמס‬
‫בתו‪ 31 $‬שלבי‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 84‬‬

‫א‪ .‬אז מהי )בדיוק( רקורסיה?‬


‫באופ כללי‪ ,‬רקורסיה היא הגדרה של מושג או של אלגורית )כלומר של פתרו לבעיה(‪,‬‬
‫המשתמשת במושג או באלגורית המוגדר‪ ,‬עצמו‪ .‬בבואנו לתכנת אנו מעונייני בעיקר בהגדרה‬
‫רקורסיבית של אלגוריתמי‪ .‬אלגורית רקורסיבי הוא אלגורית שמפעיל את עצמו פע אחת או‬
‫יותר על מופעי אחרי של בעיה שברצוננו לפתור‪ ,‬א הבעיה אינה פשוטה דיה לפתרו ישיר‪ .‬כדי‬
‫שהגדרה רקורסיבית של אלגורית )פתרו( תוביל לפתרו‪ ,‬חשוב והכרחי שמופעי הבעיה הנפתרי‬
‫בהפעלות הרקורסיביות יהיו "קטני יותר" או פשוטי יותר מהמופע המקורי‪ .‬תנאי זה מבטיח‬
‫שתהליכי הפירוק מגיעי בסופו של דבר לבעיות פשוטות שנית לפתור באופ ישיר‪.‬‬
‫לבעיה של חפיסת השוקולד הצענו פתרו רקורסיבי‪ :‬פתרנו את הבעיה באמצעות חלוקתה לשתי‬
‫בעיות דומות‪ ,‬שגודל כל אחת מה הוא מחצית הבעיה המקורית‪ .‬ג בעיות אלה נפתרו על ידי‬
‫חלוקה לבעיות דומות שגודל קט בחצי )דהיינו שגודל הוא רבע הבעיה המקורית(‪ ,‬וכ‪ $‬הלאה‪ ,‬עד‬
‫שהגענו למקרה "בסיסי"‪ ,‬שאי עוד סיבה להמשי‪ $‬ולפרק לבעיות קטנות יותר‪ .‬בבעיית השוקולד‬
‫המקרה הבסיסי ברור‪ :‬כאשר נשארת ביד קובייה אחת בלבד‪ ,‬אי צור‪ $‬להמשי‪ $‬ולחלק‪ .‬בשלב זה‬
‫אוכלי את השוקולד‪.‬‬

‫בהמש‪ $‬נראה שלמרבית הפתרונות הרקורסיביי שניתקל בה יש מאפייני דומי‪ :‬נית‬
‫להגדיר באמצעות מקרה בסיסי ומקרה מורכב‪ .‬המקרה הבסיסי "קל" להבנה‪ ,‬והוא נית‬
‫לפתרו ישיר‪ .‬את המקרה המורכב נית להגדיר במונחי פשוטי יותר‪ ,‬בעלי אפיו זהה לאפיו‬
‫של הבעיה המקורית‪ .‬כלומר‪ ,‬הבעיה המקורית ניתנת לחלוקה למקרי פשוטי יותר ממנה‪ ,‬שה‬
‫עדיי אינ בסיסיי אלא מורכבי‪ .‬את המקרי המורכבי האלה אפשר לחלק למקרי פשוטי‬
‫עוד יותר עד שמגיעי להגדרה של המקרה הבסיסי שפתרונו פשוט וידוע‪ .‬חשיבות הפירוק‬
‫למקרי פשוטי יותר של הבעיה ברורה‪ :‬דבר זה מבטיח ששרשרת ההפעלות הרקורסיביות‬
‫תסתיי במקרי בסיסיי הנפתרי ישירות‪ ,‬ולכ האלגורית הרקורסיבי יוצר תהלי‪ $‬רקורסיבי‬
‫סופי‪ .‬שרשרת ההפעלות נפסקת כאשר הרקורסיה מגיעה למקרה הבסיסי‪ ,‬ולכ המקרה הבסיסי‬
‫נקרא ג תנאי עצירה‪.‬‬

‫ב‪ .‬פתרו רקורסיבי של בעיה פשוטה‬


‫ב‪ .1.‬הדפסת מספר במהופ‬
‫ברצוננו להדפיס את ספרותיו של מספר של וחיובי‪ ,‬במהופ‪) $‬מהסו( להתחלה(‪ ,‬מבלי להשתמש‬
‫במער‪ $‬או בצורת אחסו אחרת‪ .‬לש פתרו הבעיה נגדיר אלגורית רקורסיבי‪.‬‬
‫תחילה נזהה את המקרה הבסיסי‪ :‬א המספר הוא בי ‪ 1‬ל‪ ,9‬אזי הוא מיוצג על ידי ספרה אחת‪.‬‬
‫במקרה זה נפתור את הבעיה על ידי הדפסת המספר‪.‬‬
‫ ‪ 85‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫א המספר גדול מ‪ ,9‬אזי הוא מיוצג על ידי שתי ספרות לפחות‪ ,‬ולפנינו מקרה מורכב‪ .‬תהלי‪$‬‬
‫הפתרו ייראה כ‪ :$‬תחילה נדפיס את ספרת האחדות שלו )זו הספרה הימנית ביותר בייצוגו(‪,‬‬
‫אחריה נדפיס בסדר הפו‪ $‬את המספר המיוצג על ידי הספרות של המספר הנתו בהשמטת ספרה‬
‫זו‪ .‬למשל‪ ,‬להדפסת הספרות של ‪ 1849‬בסדר הפו‪ ,$‬נדפיס את ‪ 9‬ואז נדפיס את הספרות של ‪184‬‬
‫בסדר הפו‪ .$‬דבר זה נעשה כמוב על ידי הפעלה רקורסיבית של אותו תהלי‪ .$‬שימו לב שהפרמטר‬
‫הנשלח לפעולה בהפעלה הרקורסיבית קט מהפרמטר המקורי‪ ,‬כיו שהוא התקבל ממנו על ידי‬
‫השמטת ספרת האחדות‪.‬‬

‫כיצד נבצע את הפירוק שתיארנו? נניח שהמספר הנתו הוא ‪ ,n‬והוא גדול מ‪ .9‬את ספרת האחדות‬
‫שלו נית לקבל על ידי ביצוע הפעולה ‪ ,n%10‬המחזירה את השארית של חלוקת ‪ n‬ב‪ .10‬שארית זו‬
‫היא כמוב מספר ב ספרה אחת בלבד‪ .‬אל המספר המתקבל מ‪ n‬לאחר שהושמטה ספרת האחדות‬
‫נוכל להגיע על ידי ביצוע הפעולה ‪ n/10‬והכנסת התוצאה למשתנה מטיפוס של‪.‬‬

‫נסכ את מאפייני הרקורסיה בבעיה הנתונה )אנו מסמני את המספר הטבעי הנתו ב‪:(n>0 ,n‬‬

‫מקרה בסיסי )=תנאי עצירה(‪ .n<10 :‬הפתרו הוא הדפסת המספר‪.‬‬

‫מקרה מורכב‪ :‬א ‪ ,n>9‬הייצוג של ‪ n‬הוא ב יותר מספרה אחת‪ .‬הפתרו יבודד את ספרת האחדות‬
‫וידפיס אותה ישירות‪ ,‬ואחר כ‪ $‬יזמ את הפעולה באופ רקורסיבי על המספר הנותר המיוצג על‬
‫ידי יתר הספרות‪ ,‬לאחר שקופדה ספרת האחדות שלו‪.‬‬

‫ננסח את האלגורית‪:‬‬
‫הדפס במהופ )‪(n‬‬
‫א ‪ ,n<10‬הדפס את ‪n‬‬
‫אחרת‪ :‬הדפס את ‪) n%10‬השארית של חלוקת ‪ n‬ב‪(10‬‬
‫זמ את הדפס במהופ )‪(n/10‬‬
‫ֵ‬

‫להל פעולה המממשת את האלגורית‪:‬‬


‫)‪public static void reverse(int n‬‬
‫{‬
‫)‪if (n<10‬‬
‫;)‪System.out.println(n‬‬
‫‪else‬‬
‫{‬
‫;)‪System.out.print(n%10‬‬
‫;)‪reverse (n/10‬‬
‫}‬
‫}‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 86‬‬

‫נעקוב אחרי ביצוע האלגורית בעזרת האיור והטבלה‪ .‬הפרמטר הוא המספר ‪:357‬‬

‫הדפס במהופך )‪(357‬‬


‫אם ‪... n<10‬‬
‫אחרת‪ :‬הדפס )‪(357%10‬‬ ‫הדפס במהופך )‪(35‬‬
‫זמן‪ :‬הדפס במהופך )‪(35‬‬
‫ֵ‬ ‫אם ‪... n<10‬‬
‫אחרת‪ :‬הדפס )‪(35%10‬‬ ‫הדפס במהופך )‪(3‬‬
‫זמן‪ :‬הדפס במהופך )‪(3‬‬
‫ֵ‬ ‫אם ‪n<10‬‬
‫הדפס )‪(n‬‬

‫פ ל ט‬ ‫‪7‬‬ ‫‪5‬‬ ‫‪3‬‬

‫הער לזימו‬ ‫בדיקת תנאי‬


‫הרקורסיבי‬ ‫הפעולה המתבצעת‬ ‫העצירה‬ ‫הפרמטר‬ ‫מספר ההפעלה‬

‫)‪35  (357/10‬‬ ‫הדפס )‪7  (357%10‬‬ ‫)‪! (n < 10‬‬ ‫‪n = 357‬‬ ‫הפעלה ‪1‬‬
‫)‪3  (35/10‬‬ ‫הדפס )‪5  (35%10‬‬ ‫)‪! (n < 10‬‬ ‫‪n = 35‬‬ ‫הפעלה ‪2‬‬
‫סו( הרקורסיה‬ ‫‪3‬‬ ‫הדפס‬ ‫‪n < 10‬‬ ‫‪n=3‬‬ ‫הפעלה ‪3‬‬

‫שימו לב כי כאשר זימנו את הפעולה הרקורסיבית בפע הראשונה‪ ,‬הפרמטר לא התאי למקרה‬
‫הבסיסי )תנאי העצירה(‪ ,‬אול‪ ,‬בסופו של דבר תזומ הפעולה הרקורסיבית עבור הפרמטר‬
‫שמתאי למקרה זה והתהלי‪ $‬ייעצר‪ .‬א לא נגדיר מקרה בסיסי‪ ,‬או א ההתכנסות לקראת תנאי‬
‫העצירה לא תיכתב כראוי‪ ,‬ייווצרו זימוני חדשי בזה אחר זה‪ .‬בגלל ריבוי הקריאות בריצת‬
‫הפעולה‪ ,‬תזהה מערכת ג'אווה את הבעיה אחרי פרק זמ מסוי‪ ,‬תשלח הודעת שגיאה ותפסיק את‬
‫ביצוע הפעולה‪.‬‬

‫? נסו לכתוב את הפעולה )…(‪ reverse‬ללא תנאי העצירה וראו מה יקרה‪.‬‬

‫המשימה של הדפסת המספר במהופ‪ $‬הסתיימה כאשר זימנו את הפעולה הרקורסיבית ע מספר‬
‫חדספרתי )המקרה הבסיסי(‪.‬‬
‫ ‪ 87‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫סביר לממש את הפעולה ‪ reverse‬כפעולה סטטית הפועלת על מספר של‪ .‬נית לכתוב את הפעולה‬
‫הרקורסיבית הסטטית במחלקת שירות או בקוב‪ -‬שבו מופיעה הפעולה הראשית ולזמ אותה‬
‫מתו‪ $‬הפעולה הראשית )או מכל פעולה אחרת(‪ ,‬כמו בדוגמה‪:‬‬

‫‪public class TestReverse‬‬


‫{‬
‫)‪public static void main(String[] args‬‬
‫{‬
‫;)‪reverse(4521‬‬
‫}‬
‫)‪public static void reverse(int n‬‬
‫{‬
‫…‬
‫}‬
‫}‬

‫אול‪ ,‬באופ כללי‪ ,‬פעולה רקורסיבית אינה חייבת להיות סטטית‪ .‬סיווג פעולה כרקורסיבית‬
‫מציי את האופ שבו אנו משתמשי בפעולה‪ .‬בג'אווה אי הבדל בי פעולה רקורסיבית לפעולה‬
‫לא רקורסיבית‪ .‬לכל הפעולות‪ ,‬רקורסיביות או לא‪ ,‬אות צורות זימו‪ .‬כל מה שכבר למדנו ביחידה‬
‫על פעולות‪ ,‬או שנלמד בהמש‪ ,$‬תק( בפרט ג לפעולות רקורסיביות‪.‬‬

‫ישנ פתרונות רקורסיביי מורכבי יותר‪ ,‬ובה ג לאחר זימו הפעולה הרקורסיבית האחרונה‬
‫יש המש‪ $‬של תהלי‪ $‬הפתרו‪ .‬בדוגמה הבאה נראה בעיה שכזו‪.‬‬

‫ב‪ .2.‬בעיית העצרת‬


‫העצרת )‪ (factorial‬של מספר טבעי כלשהו )‪ ,(0<n‬מסומנת כ‪ n! :$‬והיא מוגדרת כמכפלת כל‬
‫המספרי בי ‪ 1‬ל‪.n‬‬
‫לדוגמה‪:‬‬

‫‪1! = 1‬‬
‫‪2! = 1 * 2 = 2‬‬
‫‪3! = 1 * 2 * 3 = 6‬‬
‫‪4! = 1 * 2 * 3 * 4 = 24‬‬
‫‪5! = 1 * 2 * 3 * 4 * 5 = 120‬‬
‫‪...‬‬
‫‪n! = 1 * 2 * 3 *…* (n-1) * n‬‬ ‫)עבור ‪ n‬כלשהו(‬

‫כמו כ‪ ,‬מקובל להגדיר את !‪ 0‬כ‪.1‬‬

‫נית לחשב את העצרת בחישוב איטרטיבי‪ ,‬א‪ $‬בפרק זה אנו מעונייני להציג חישוב רקורסיבי‪.‬‬
‫כדי לכתוב אלגורית רקורסיבי לפתרו בעיית חישוב העצרת של מספר נתו‪ ,‬נציג הגדרה נוספת‬
‫של המושג עצרת‪ ,‬והפע הגדרה רקורסיבית‪ .‬מהגדרה זו נקבל בקלות חישוב רקורסיבי‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 88‬‬

‫נשווה את העצרת של שני מספרי עוקבי‪ ,‬למשל ‪ 4‬ו‪:5‬‬

‫!‪1 * 2 * 3 * = 4! ;1 * 2 * 3 * 4 * 5 = 5‬‬ ‫על פי ההגדרה‪:‬‬


‫‪4‬‬
‫מכא ברור ש‪ . 4! * 5 = 5! :‬שימו לב‪ ,‬שהמסקנה נכונה באופ כללי‪ ,‬שהרי ג !‪ 5! * 6 = 6‬וכ‬
‫הלאה‪ ,‬ובמקרה המורכב‪.(n-1)! * n = n! :‬‬

‫א נוסי( להבנה זו את העובדה ש‪) 0! = 1 :‬המקרה הבסיסי(‪ ,‬נקבל הגדרה רקורסיבית של‬
‫העצרת של מספר טבעי‪:‬‬

‫העצרת )‪ (factorial‬של מספר טבעי ‪ n‬מסומנת כ !‪n‬‬


‫ומוגדרת כ‪:$‬‬
‫א ‪) n = 0‬המקרה הבסיסי(‪ ,‬ער‪ $‬העצרת הוא ‪1‬‬ ‫•‬
‫א ‪) n > 0‬המקרה המורכב(‪ ,‬ער‪ $‬העצרת הוא המכפלה‪(n-1)! * n :‬‬ ‫•‬

‫אלגורית רקורסיבי לבעיית העצרת‬


‫מצוידי בהבנת המבנה הרקורסיבי של העצרת‪ ,‬נוכל לכתוב בקלות אלגורית לחישובה‪:‬‬

‫חשב עצרת )‪(n‬‬


‫א ‪ ,n = 0‬החזר ‪1‬‬
‫אחרת‪ ,‬החזר את המכפלה של ‪ n‬בתוצאה המוחזרת מזימו חשב עצרת )‪(n-1‬‬

‫ובקוד‪ ,‬בהנחה ש‪:n ≥ 0 :‬‬

‫)‪public static int factorial(int n‬‬


‫{‬
‫)‪if (n == 0‬‬
‫;‪return 1‬‬
‫‪else‬‬
‫;))‪return (n * factorial(n - 1‬‬
‫}‬

‫שימו לב שהקריאה הרקורסיבית נעשית על ידי זימו עצמי של הפעולה‪ ,‬כלומר הפעולה מזמנת את‬
‫עצמה‪ ,‬א‪ $‬ע פרמטר שונה‪ .‬על מנת להבי כיצד פועלת הרקורסיה‪ ,‬נעקוב אחרי מהל‪ $‬ביצוע‬
‫החצי באיור מסמני את שני שלבי הביצוע‬
‫הפעולה בעזרת האיור הבא‪ ,‬עבור ער‪ $‬הפרמטר ‪ִ .4‬‬
‫של אלגורית זה‪ .‬הראשו הוא שלב "הלו‪ ,"$‬שבו מתבצעי זימוני רקורסיביי בזה אחר זה‪,‬‬
‫‪ .‬השלב השני הוא שלב "חזור"‪ ,‬שבו מוחזרי ערכי לחישובי שממתיני‬ ‫והוא מסומ בח‪-‬‬
‫‪.‬‬ ‫והוא מסומ בח‪ -‬הפו‪$‬‬
‫ ‪ 89‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫)‪factorial (4): 4 * factorial (3‬‬


‫)‪factorial (3): 3 * factorial (2‬‬
‫)‪factorial (2): 2 * factorial (1‬‬
‫)‪factorial (1): 1 * factorial (0‬‬
‫‪factorial (0):‬‬ ‫‪return 1‬‬
‫‪factorial (1): 1 * 1, return 1‬‬
‫‪factorial (2): 2 * 1, return 2‬‬
‫‪factorial (3): 3 * 2, return 6‬‬
‫‪factorial (4): 4 * 6, return 24‬‬
‫סו‪ $‬הרקורסיה‪ :‬הער המוחזר הוא ‪24‬‬

‫בשורה הראשונה‪ ,‬הער‪ $‬של ‪ n‬הוא ‪ .4‬כיוו שער‪ $‬זה אינו ‪ ,0‬יש לבצע כפל של ‪ n‬ב )‪.factorial (n-1‬‬
‫כדי לקבל את הער‪ $‬של )‪ factorial (n-1‬הפעולה מחשבת את ער‪ ,n-1 $‬מקבלת ‪ ,3‬ומבצעת זימו‬
‫רקורסיבי של הפעולה ‪ factorial‬ע ער‪ $‬הפרמטר ‪ .3‬החישוב מופסק והפעולה ממתינה לתוצאות‬
‫זימו זה‪.‬‬

‫בשורה השנייה מתוארת ההפעלה עבור ער‪ $‬הפרמטר ‪ ,3‬בביצוע דומה‪ :‬כדי לחשב את ‪factorial‬‬
‫)‪ ,(3‬יש לזמ באופ רקורסיבי את )‪ ,factorial (2‬וכ‪ $‬הלאה עד לשורה החמישית‪ ,‬שבה ער‪$‬‬
‫הפרמטר בזימו המתואר בשורה הוא ‪.0‬‬

‫החישוב של )‪ factorial (0‬הוא חישוב של המקרה הבסיסי )שכ הפרמטר הוא ‪ ,(0‬ועל כ התוצאה‬
‫שלו היא ‪ .1‬תוצאה זו מוחזרת לזימו הקוד‪ ,‬של )‪ ,factorial (1‬שהיה בהמתנה‪ .‬משלב זה זימו‬
‫זה ממשי‪ ,$‬מכפיל ‪ 1‬ב‪ ,1‬ומחזיר ‪ 1‬לזימו )‪ .factorial (2‬זה מחדש את פעולתו‪ ,‬מכפיל ‪ 1‬ב‪,2‬‬
‫ומחזיר ‪ .2‬כ‪ $‬מוחזרי ערכי לזימוני הממתיני‪ ,‬בזה אחרי זה‪ .‬כל זימו מכפיל את ער‪$‬‬
‫הפרמטר שלו בתוצאה שהוחזרה אליו‪ ,‬ומחזיר את התוצאה לזימו שהפעיל אותו‪ .‬כאשר הזימו‬
‫הראשו מחזיר את התוצאה שלו‪ ,‬שהיא ‪ ,24‬החישוב מסתיי‪.‬‬
‫שימו לב להבדל בי התהליכי הרקורסיביי בשתי הבעיות שהצגנו‪ :‬הפיכת ספרות של מספר‬
‫ובעיית העצרת‪ .‬בהפיכת ספרות של מספר ביצענו רק את תהלי‪ $‬ה"הלו‪ – "$‬הדפסנו את ספרת‬
‫האחדות ואז זימנו את הפעולה הרקורסיבית‪ .‬כאשר הסתיימה הקריאה האחרונה‪ ,‬סיימנו את‬
‫התהלי‪ $‬הרקורסיבי‪ .‬לעומת זאת‪ ,‬בחישוב העצרת היינו צריכי להחזיר את הער‪ $‬של זימו‬
‫הפעולה ולבצע הכפלה נוספת ב‪ .n‬לכ‪ ,‬כאשר פעולה מזמנת את עצמה ע ער‪ $‬פרמטר קט יותר‪,‬‬
‫היא נשארת בהמתנה לער‪ $‬שיוחזר מהזימו הרקורסיבי‪ .‬כאשר מסתיי הזימו‪ ,‬הוא מחזיר ער‪,$‬‬
‫והפעולה הממתינה "מתעוררת לחיי" וממשיכה בחישוב‪ ,‬עד שהיא מסיימת ומחזירה ער‪.$‬‬
‫בדוגמה שראינו כל הזימוני התבצעו בזה אחר זה‪ ,‬ואחריה התבצעו החזרות הערכי‪ .‬בתהלי‪$‬‬
‫רקורסיבי זה החלוקה לשני שלבי ברורה‪" ,‬הלו‪ "$‬ואחריו "חזור"‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 90‬‬

‫ג‪ .‬אלגורית רקורסיבי לחישוב מספרי ִפי'( ָנ ִצ'י‬


‫סדרת פיבונצ'י היא סדרת מספרי הקרויה על שמו של לאונרדו ִפי>‪ָ #‬נ ִצ'י‪ ,‬המתמטיקאי המערבי‬
‫הראשו שגילה אותה לפני כ‪ 800‬שנה‪ .‬הסדרה מוגדרת בדר‪ $‬הבאה‪ :‬שני האיברי הראשוני‬
‫בסדרה ה ‪ 0‬ו‪ ,1‬כל איבר נוס( בסדרה הוא סכומ של שני האיברי הקודמי לו בסדרה‪.‬‬

‫‪ 12‬האיברי הראשוני של סדרת פיבונצ'י ה‪:‬‬

‫‪0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 . . .‬‬

‫א נסמ את הער‪ $‬של המספר במקו ה‪ k‬בסדרת פיבונצ'י כ‪ :‬מספר פיבונצ'י )‪ ,(k‬נקבל הגדרה‬
‫רקורסיבית של ער‪ $‬כל מספר בסדרה‪:‬‬

‫מספר פיבונצ'י )‪:(k‬‬


‫א ‪ ,k = 1‬הער‪ $‬הוא ‪0‬‬
‫א ‪ ,k = 2‬הער‪ $‬הוא ‪1‬‬
‫אחרת‪ ,‬הער‪ $‬הוא מספר פיבונצ'י )‪ + (k - 1‬מספר פיבונצ'י )‪(k - 2‬‬

‫זוהי הגדרה רקורסיבית כיוו שהיא מגדירה כל איבר‪ ,‬פרט לשני הראשוני‪ ,‬באמצעות שני‬
‫האיברי הקודמי‪ .‬מהגדרה זו קל לקבל אלגורית רקורסיבי לחישוב מספרי פיבונצ'י‪ .‬כדי‬
‫לעשות זאת‪ ,‬המושג 'הער‪ $‬הוא' יתורג באלגורית להנחיה 'החזר'‪.‬‬

‫ובהנחה ש‪ , k≥1 :‬הקוד ייראה כ‪:$‬‬

‫)‪public static int fibonacci(int k‬‬


‫{‬
‫)‪if (k == 1‬‬
‫;‪return 0‬‬
‫)‪if (k == 2‬‬
‫;‪return 1‬‬
‫;))‪return (fibonacci(k-1) + fibonacci(k-2‬‬
‫}‬

‫נעקוב אחרי הביצוע של )‪ ,int x = fibonacci (4‬בעזרת האיור הבא‪:‬‬


‫‪ .‬פעולת החיבור‪ ,‬הפעולה החשבונית שיש‬ ‫כל זימו של הרקורסיה מצוי בח‪ -‬שחור מלא‬
‫צור‪ $‬לבצע בי ערכי הזימוני השוני )כשיחושבו ויוחזרו(‪ ,‬מסומנת בשורה המתחילה‬
‫הפוני חזרה אל מי שזימ‬ ‫החצי המקווקווי‬
‫ב"אחרת‪ .":‬ערכי החזרה מסומני על גבי ִ‬
‫את הרקורסיה‪ .‬ע החזרת הערכי מתבצעת פעולת החיבור‪ ,‬והער‪ $‬המתקבל מוחזר שוב "כלפי‬
‫מעלה"‪ ,‬למי שזימ את החישוב‪ .‬כיוו שהקריאה הרקורסיבית מתבצעת פעמיי‪ ,‬נבצע את המעקב‬
‫בעזרת דג "ע‪ "-‬שיאפשר לנו להסתע( לכיווני שוני על פי התפתחות הרקורסיה‪:‬‬
‫ ‪ 91‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫כפי שנראה מהאיור‪ ,‬חלק מהחישובי הנעשי ה מיותרי‪ .‬שוב ושוב נדרשת הרקורסיה לתת‬
‫מענה על ערכ של )‪ fibonacci (1‬ושל )‪ ,fibonacci (2‬א( על פי שאלו חושבו כבר‪ .‬ככל שהמספר‬
‫הראשו שישלח לפעולה יהיה גדול יותר‪ ,‬כ‪ $‬יגדל מספר של החישובי המיותרי החוזרי על‬
‫עצמ‪ .‬נראה שפתרו רקורסיבי לסדרת פיבונצ'י אינו פתרו אידיאלי‪ ,‬ואינו חוס‪ $‬במשאבי‬
‫השוני‪.‬‬
‫? כאשר מזמני את ‪ fibonacci‬עבור הער‪ ,5 $‬כמה פעמי מחושב )‪ fibonacci (1‬וכמה פעמי‬
‫מחושב )‪?fibonacci (2‬‬

‫ד‪ .‬סוגי רקורסיה‬


‫ד‪ .1.‬רקורסיית "זנב" מול רקורסיה "הלו חזור"‬
‫ראינו שלוש דוגמאות של רקורסיה‪ ,‬כל אחת שונה במבנה או בהתנהגות מהאחרות‪ .‬בדוגמה‬
‫הראשונה )היפו‪ $‬הספרות של המספר(‪ ,‬יש באלגורית קריאה רקורסיבית יחידה‪ .‬הרקורסיה‬
‫מבצעת תהלי‪" $‬הלו"‪ ,‬כלומר סדרה אחת של קריאות רקורסיביות‪ .‬התהלי‪ $‬מסתיי אחרי תו‬
‫ביצוע הקריאה הרקורסיבית האחרונה‪ .‬רקורסיה אשר מבצעת רק תהלי‪ $‬של "הלו‪ "$‬נקראת‬
‫"רקורסיית זנב"‪.‬‬

‫ג חישוב העצרת מתבצע על ידי אלגורית שבו קריאה רקורסיבית יחידה‪ ,‬אול התהלי‪$‬‬
‫הרקורסיבי הזה הוא תהלי‪" $‬הלו חזור"‪ .‬אחרי שמתבצעת הקריאה הרקורסיבית האחרונה‪,‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 92‬‬

‫הער‪ $‬שח!שב מוחזר לשלב הקוד של הרקורסיה‪ ,‬והוא בתורו מוחזר להמש‪ $‬החישוב‪ ,‬וכ‪$‬‬
‫הלאה‪.‬‬

‫נית לזהות רקורסיית "הלו‪$‬חזור" א נענה בחיוב על השאלה‪" :‬הא אחרי הקריאה‬
‫הרקורסיבית יש לבצע משהו נוס(?"‪ .‬בדוגמה שראינו‪ ,‬בחישוב העצרת‪ ,‬יש להכפיל את התוצאה‬
‫ב‪ ,n‬זוהי רקורסיית "הלו‪$‬חזור"‪ ,‬לעומת זאת בהיפו‪ $‬הספרות של המספר אי צור‪ $‬לבצע משהו‬
‫נוס( ולכ זו אינה רקורסיית "הלו‪$‬חזור"‪.‬‬

‫ד‪ .2.‬רקורסיה כפולה‬


‫בניגוד לשתי הדוגמאות הקודמות‪ ,‬בדוגמה של מספרי פיבונצ'י יש בגו( האלגורית שתי קריאות‬
‫רקורסיביות‪ :‬לחישוב מספר פיבונצ'י במקו ‪ k-1‬ולחישוב מספר פיבונצ'י במקו ‪ .k-2‬רקורסיה‬
‫כזו נקראת "רקורסיה כפולה"‪ .‬ג בדוגמה זו‪ ,‬אחרי שמוחזרי הערכי של הקריאות‬
‫הרקורסיביות‪ ,‬קיימת פעולה נוספת שיש לבצע‪ :‬חיבור הערכי‪ .‬אפשר לומר שזו היא רקורסיית‬
‫"הלו‪$‬חזור"‪ ,‬א‪ $‬פריסתה דומה יותר לפריסה של ע‪ -‬מאשר לפריסה של תהלי‪" $‬הלו‪ "$‬רצי(‬
‫אחד‪ ,‬שבסופו נפרס תהלי‪" $‬חזור" רצי(‪ .‬במקרה זה זימוני והחזרות ערכי שלובי אלה באלה‬
‫בתהלי‪ $‬אחד‪.‬‬

‫כמוב שאלה אינ כל הסוגי האפשריי של רקורסיה‪ ,‬א‪ $‬ה מדגימי שני קריטריוני‬
‫חשובי בניתוח אלגורית רקורסיבי‪ :‬א( מספר הקריאות הרקורסיביות בגו( האלגורית; ב(‬
‫הא אחרי קריאה רקורסיבית צרי‪ $‬לבצע עוד חישוב בפעולה המזמנת‪ .‬חשוב להדגיש שלא בכל‬
‫מקרה שקיימת רק קריאה רקורסיבית אחת באלגורית‪ ,‬מדובר בהכרח ברקורסיית זנב או‬
‫רקורסיית הלו‪$‬חזור‪ .‬א הקריאה היחידה מופיעה בתו‪ $‬לולאה‪ ,‬ייתכ שתתבצע פעמי רבות‪.‬‬

‫להל דוגמה‪:‬‬

‫)‪public static int mysterySum(int n‬‬


‫{‬
‫)‪if (n == 1‬‬
‫;‪return 1‬‬
‫;‪int sum = n‬‬
‫)‪for (int i = 1; i < n; i++‬‬
‫;)‪sum = sum + mysterySum(i‬‬
‫;‪return sum‬‬
‫}‬

‫? מה מחזירה הפעולה )…( ‪ mysterySum‬עבור ‪?n = 3‬‬

‫ד‪ .3.‬רקורסיה הדדית‬


‫במצבי שוני משתתפות ברקורסיה שתי פעולות שונות‪ .‬א הפעולה )(‪ f‬מזמנת את פעולה )(‪,g‬‬
‫וזו מזמנת את )(‪ ,f‬אזי הפעולות )(‪ f‬ו)(‪ g‬ה פעולות רקורסיביות‪ ,‬ג א א( פעולה לא מזמנת את‬
‫עצמה ישירות‪ .‬רקורסיה שכזו נקראת "רקורסיה הדדית"‪ .‬א נסתכל על שתי הפעולות נראה‬
‫שיחד ה מבצעות תהלי‪ $‬רקורסיבי‪ ,‬המפוצל לשתי פעולות‪.‬‬
‫ ‪ 93‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫לדוגמה‪ ,‬נראה את בעיית העצרת‪ ,‬אלא שהפע היא נכתבת כרקורסיה הדדית של שתי הפעולות‪:‬‬
‫)…( ‪ factorial‬ו)…( ‪:g‬‬
‫)‪public static int factorial(int n‬‬
‫{‬
‫)‪if (n == 0‬‬
‫;‪return 1‬‬
‫‪else‬‬
‫;)‪return g(n‬‬
‫}‬

‫)‪public static int g(int n‬‬


‫{‬
‫;)‪return n * factorial(n-1‬‬
‫}‬

‫ה‪ .‬רקורסיה על טיפוסי נתוני אחרי ‬


‫כל הדוגמאות הקודמות הציגו פתרונות רקורסיביי לבעיות המטפלות במספרי‪ .‬נבח עתה‬
‫פתרונות רקורסיביי לבעיות שהפרמטר שלה הוא מסוג מחרוזת או מער‪ .$‬לא נציג כא סוגי‬
‫חדשי של רקורסיה‪ .‬העניי בדוגמאות אלה הוא בהגדרת המקרי הפשוטי והמורכבי‪,‬‬
‫והמעבר ממקרה מורכב לפשוט יותר‪ ,‬עבור טיפוסי נתוני השוני מ‪.int‬‬

‫ה‪ .1.‬רקורסיה על מחרוזות‬


‫כדי לעבור ממחרוזת למחרוזת קטנה ממנה‪ ,‬נית להוריד מהמחרוזת ההתחלתית תו אחד או‬
‫יותר‪ ,‬בעזרת הפעולות הקיימות במחלקה ‪) String‬ראו ב‪.(API‬‬

‫דוגמה‪ :‬הא מחרוזת היא פלינדרו ‬


‫פלינדרו הוא מחרוזת שקריאתה משמאל לימי ומימי לשמאל מחזירה מחרוזת זהה‪ .‬למשל‪,‬‬
‫המחרוזות "אבא" ו"כצפרשתשרפצכ" ה פלינדרומי‪.‬‬

‫בעיה‪ :‬כתבו פעולה המקבלת מחרוזת ומחזירה 'אמת' א המחרוזת הנתונה היא פלינדרו‪,‬‬
‫ו'שקר' אחרת‪.‬‬

‫פתרו‪ :‬נית לפתור בעיה זו ג על ידי פעולה שאינה רקורסיבית‪ ,‬למשל על ידי לולאה שתשווה כל‬
‫זוג תווי הנמצאי באותו מרחק מתחילת המילה ומסופה‪ ,‬א‪ $‬לצור‪ $‬התרגול של פרק זה נעצב‬
‫פתרו רקורסיבי‪.‬‬

‫המקרה הבסיסי‪ :‬א המחרוזת מכילה תו אחד או אינה מכילה תווי כלל‪ ,‬אזי היא פלינדרו‪.‬‬

‫המקרה המורכב‪ :‬א המחרוזת מכילה יותר מתו אחד‪ ,‬אזי‪ :‬א התו האחרו זהה לתו הראשו‬
‫וא המחרוזת ללא תווי אלה )הראשו והאחרו(‪ ,‬מהווה פלינדרו‪ ,‬אזי המחרוזת כולה היא‬
‫פלינדרו‪.‬‬

‫קל לראות כי בקריאה הרקורסיבית הפרמטר קט יותר מזה שבקריאה המקורית‪ ,‬שכ שני תווי‬
‫הורדו משני צדי המחרוזת‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 94‬‬

‫תזכורת‪ :‬הפעולות הבאות לעבודה ע מחרוזות לקוחות מתו‪ $‬ממשק המחלקה ‪:String‬‬

‫‪String substring (int beginIndex,‬‬ ‫הפעולה מחזירה תתמחרוזת המתחילה‬


‫ב‪ beginIndex‬ומסתיימת ב‪ ,endIndex‬א‪$‬‬
‫)‪int endIndex‬‬ ‫אינה כוללת את ‪ endIndex‬עצמו‬

‫)(‪int length‬‬ ‫הפעולה מחזירה את אור‪ $‬המחרוזת‬

‫)‪char charAt (int index‬‬ ‫הפעולה מחזירה את התו שבמקו ‪index‬‬

‫)‪public static boolean palindrome(String str‬‬


‫{‬
‫)‪if (str.length() <= 1‬‬
‫;‪return true‬‬
‫))‪if (str.charAt (0) != str.charAt(str.length()-1‬‬
‫;‪return false‬‬
‫;))‪return palindrome(str.substring(1, str.length()-1‬‬
‫}‬

‫שימו לב‪ ,‬תנאי העצירה בדוגמה זו כולל ג את המקרה של מחרוזת ריקה )‪ 0‬תווי( וג את‬
‫המקרה של מחרוזת בת תו אחד‪.‬‬

‫לגבי המקרה המורכב‪ ,‬א במחרוזת מספר איזוגי של תווי‪ ,‬ואנו מקטיני את הבעיה על ידי‬
‫הורדת התו האחרו והתו הראשו‪ ,‬תישאר בסו( התהלי‪ $‬מחרוזת בת תו אחד‪ .‬א במחרוזת‬
‫מספר זוגי של תווי‪ ,‬אזי בסו( התהלי‪ $‬תישאר מחרוזת ריקה שבה ‪ 0‬תווי‪ .‬בשני המקרי‪ ,‬א‬
‫הגענו לשלב הזה‪ ,‬סימ שהמחרוזת היא פלינדרו‪.‬‬

‫מעקב‪ :‬נעקוב אחר התוכנית‪ ,‬עבור המחרוזת "‪ ,"ABCDA‬בעזרת ִ‬


‫החצי המתארי את תהלי‪$‬‬
‫הכניסה והיציאה מהרקורסיה )"הלו‪$‬חזור"(‪ .‬שימו לב שהגדרת הרקורסיה כרקורסיית "הלו‪$‬‬
‫חזור"‪ ,‬נובעת הפע מכ‪ $‬שע סיו הזימו האחרו יש עדיי ערכי להחזיר‪ ,‬עד לקבלת התשובה‬
‫הסופית‪ .‬אמנ זהו הדבר הנוס( היחיד שיתבצע‪ ,‬א‪ $‬די בכ‪ $‬כדי להגדיר שיש כא תהלי‪ $‬של‬
‫"חזור"‪.‬‬
‫)"‪palindrome ("ABCDA") => palindrome ("BCD‬‬
‫‪palindrome ("BCD") => return false‬‬
‫‪palindrome ("ABCDA") => return false‬‬
‫הרקורסיה נגמרה ומחזירה ער 'שקר'‬
‫ ‪ 95‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫ה‪ .2.‬רקורסיה על מערכי ‬


‫פעולה רקורסיבית על מער‪ $‬פירושה בדר‪ $‬כלל שבזימו הרקורסיבי‪ ,‬כדי להקטי את הפרמטר‪,‬‬
‫מצמצמי את אור‪ $‬המער‪ $‬שפועלי עליו‪ .‬לצור‪ $‬כ‪ ,$‬בדר‪ $‬כלל‪ ,‬איננו מייצרי מער‪ $‬חדש קט‬
‫יותר )חיסכו במקו(‪ ,‬אלא מצמצמי את טווח הפעילות על המער‪ $‬באמצעות שינוי ערכי‬
‫האינדקסי‪ .‬כ‪ $‬מצמצמי את השטח שעובדי עליו ומתכנסי לקראת המקרה הבסיסי )תנאי‬
‫העצירה(‪.‬‬

‫דוגמה‪ :‬מציאת ער מקסימלי במער‬


‫בעיה זו ניתנת לפתרו ללא רקורסיה‪ ,‬ויש לשער שכבר פתרת בעיות שכאלה בעזרת סריקה של‬
‫מער‪ .$‬בפרק זה ברצוננו להציג שני פתרונות רקורסיביי לבעיה‪ ,‬מתו‪ $‬מספר פתרונות‬
‫רקורסיביי אפשריי‪.‬‬
‫פתרו ‪ :1‬נמצא את הער‪ $‬המקסימלי בי הערכי במער‪ ,$‬פרט לער‪ $‬הראשו בו‪.‬‬
‫נמצא את המקסימו שבי הער‪ $‬הראשו במער‪ ,$‬לער‪ $‬המקסימלי שמצאנו‪.‬‬

‫דוגמה‪ :‬נבח את המער‪ $‬הבא שבו מאוחסני הערכי‪4, 6, 9, 7, 1, 5, 8 :‬‬


‫הער‪ $‬הראשו הוא ‪ 4‬והוא מאוחס בתא שהאינדקס שלו הוא ‪ .0‬המקסימו בי יתר הערכי‬
‫)החל מהמקו השני( הוא ‪ .9‬המקסימו בי ‪ 4‬ל‪ 9‬הוא ‪ ,9‬לכ זהו המקסימו בכל המער‪.$‬‬

‫כיצד נמצא את המקסימו בקרב יתר הערכי?‬

‫נית למצוא את המקסימו בעזרת לולאה‪ ,‬א‪ $‬כדי להציג פתרו רקורסיבי לבעיה נזמ באופ‬
‫רקורסיבי את הפעולה כ‪ $‬שתמצא את המקסימו במער‪ ,$‬החל מהמקו השני בו‪ ,‬וכ‪ $‬הלאה‪.‬‬

‫מהו תנאי העצירה? כאשר קטע המער‪ $‬שבידינו מכיל ער‪ $‬אחד בלבד‪ ,‬ברור שער‪ $‬זה הוא‬
‫המקסימו בקטע‪ .‬החזרת ער‪ $‬זה תסיי את תהלי‪ $‬הזימו הרקורסיבי‪.‬‬

‫לפניכ קוד הפתרו‪ .‬יש לזמ את הפעולה לראשונה ע הער‪ ,begin = 0 $‬כדי להתחיל את חיפוש‬
‫המקסימו במער‪ $‬החל מהאיבר הראשו בו‪:‬‬

‫)‪public static int findMax(int[] arr, int begin‬‬


‫{‬
‫אורך הקטע הנוכחי במערך הוא ‪if (begin == arr.length-1) // 1‬‬
‫;]‪return arr[begin‬‬
‫‪else‬‬
‫{‬
‫;)‪int temp = findMax(arr, begin+1‬‬
‫)‪if (arr[begin] > temp‬‬
‫;]‪return arr[begin‬‬
‫;‪return temp‬‬
‫}‬
‫}‬

‫זהו אלגורית ע זימו רקורסיבי יחיד‪ .‬שימו לב כי אחרי החזרה מהזימו הרקורסיבי‪ ,‬עלינו‬
‫לבצע פעולה נוספת‪ :‬השוואת הער‪ $‬המוחזר מהקריאה והשמור ב‪ ,temp‬לער‪ .arr[begin] $‬כיוו‬
‫שכ‪ ,$‬זוהי רקורסיית "הלו‪$‬חזור"‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 96‬‬

‫מעקב‪ :‬נעקוב אחר מציאת המקסימו במער‪arr = {4 ,6 ,9 ,7 ,1, 5, 8} $‬‬

‫נחזור ונדגיש‪ :‬בכל זימו רקורסיבי נשמרי גבולות המער‪ $‬המקורי‪ .‬מיקוד ההסתכלות על‬
‫הערכי השמורי במער‪ $‬בכל שלב משתנה‪ ,‬ובאיור הוא מודגש בקו תחתי‪ .‬רעיו ההמתנה של‬
‫התהליכי עד לשוב של הערכי המחושבי )הצור‪ $‬בהמש‪ $‬החישובי(‪ ,‬מודגש באיור על ידי‬
‫כתיבת … לאחר הזימו עצמו‪:‬‬

‫… ;)‪findMax ({4, 6, 9, 7, 1, 5, 8}, 0): temp = findMax ({4, 6, 9, 7, 1, 5, 8}, 1‬‬


‫… ;)‪findMax ({4, 6, 9, 7, 1, 5, 8}, 1): temp = findMax ({4, 6, 9, 7, 1, 5, 8}, 2‬‬
‫… ;)‪findMax ({4, 6, 9, 7, 1, 5, 8}, 2): temp = findMax ({4, 6, 9, 7, 1, 5, 8}, 3‬‬
‫… ;)‪findMax ({4, 6, 9, 7, 1, 5, 8}, 3): temp = findMax ({4, 6, 9, 7, 1, 5, 8}, 4‬‬
‫… ;)‪findMax ({4, 6, 9, 7, 1, 5, 8}, 4): temp = findMax ({4, 6, 9, 7, 1, 5, 8}, 5‬‬
‫… ;)‪findMax ({4, 6, 9, 7, 1, 5, 8}, 5): temp = findMax ({4, 6, 9, 7, 1, 5, 8}, 6‬‬
‫‪findMax ({4, 6, 9, 7, 1, 5, 8}, 6) => return arr[6] = 8‬‬
‫‪findMax ({4, 6, 9, 7, 1, 5, 8}, 5) => temp = 8; arr[5] = 5, 5!>8, return 8‬‬
‫‪findMax ({4, 6, 9, 7, 1, 5, 8}, 4): temp = 8; arr[4] = 1, 1!>8, return 8‬‬
‫‪findMax ({4, 6, 9, 7, 1, 5, 8}, 3): temp = 8; arr[3] = 7, 7!>8, return 8‬‬
‫‪findMax ({4, 6, 9, 7, 1, 5, 8}, 2): temp = 8, arr[2] = 9, 9>8, return 9‬‬
‫‪findMax ({4, 6, 9, 7, 1, 5, 8}, 1): temp = 9, arr[1] = 6, 6!> 9, return 9‬‬
‫‪findMax ({4, 6, 9, 7, 1, 5, 8}, 0): temp = 9, arr[0] = 4, 4!>9, return 9‬‬
‫סו‪ $‬הרקורסיה‪ ,‬הער המוחזר הוא ‪9‬‬

‫פתרו ‪ :2‬נחלק את המער‪ $‬לשני חלקי שווי )א מספר האיברי זוגי(‪ ,‬או כמעט שווי )א‬
‫מספר האיברי אינו זוגי(‪ .‬נחפש באופ רקורסיבי את המקסימו בכל אחד מהחלקי‪ .‬אחרי‬
‫שנמצא‪ ,‬נשווה בי שני הערכי המקסימליי ונחזיר את המספר הגדול מביניה‪.‬‬

‫כפי שאמרנו קוד‪ ,‬חלוקת המער‪ $‬אינה נעשית על ידי יצירת מערכי קטני יותר‪ ,‬אלא בעזרת‬
‫מיקוד ההסתכלות על תתמערכי מתו‪ $‬המער‪ $‬המקורי‪ ,‬בעזרת האינדקסי‪ .‬לכ בנוס( למער‪,$‬‬
‫הפעולה תקבל שני פרמטרי‪ :‬אינדקס נקודת ההתחלה של תתהמער‪ $‬הנוכחי‪ ,‬ואינדקס נקודת‬
‫הסו( שלו‪.‬‬

‫מהו תנאי העצירה? כאשר הגענו לתתמער‪ $‬בגודל ‪) 1‬אינדקס ההתחלה שווה לאינדקס הסו((‪,‬‬
‫נחזיר את הער‪ $‬השמור במער‪ $‬זה‪ ,‬כמקסימו‪.‬‬
‫ ‪ 97‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫להל קוד הפתרו‪ .‬המשתמש צרי‪ $‬לזמ את הפעולה ע ערכי ‪:end = arr.length-1 ,begin = 0‬‬

‫)‪public static int findMax(int[] arr, int begin, int end‬‬


‫{‬
‫)‪if (begin == end‬‬
‫;]‪return arr[begin‬‬
‫‪else‬‬
‫{‬
‫;‪int middle = (begin + end) / 2‬‬
‫;)‪int max1 = findMax(arr, begin, middle‬‬
‫;)‪int max2 = findMax(arr, middle+1, end‬‬
‫)‪if (max1 > max2‬‬
‫;‪return max1‬‬
‫;‪return max2‬‬
‫}‬
‫}‬

‫כיוו שהמשתנה ‪ middle‬הוא מטיפוס ‪ ,int‬החלוקה ב‪ 2‬תמיד תית לנו מספר של‪ .‬עבור כל קטע‬
‫המכיל לפחות שני איברי‪ ,‬תהלי‪ $‬החלוקה של הקטע ייצור שני קטעי קצרי ממנו‪ ,‬לכ אנו‬
‫בטוחי שבסופו של דבר תמיד נגיע לתנאי העצירה )‪.(begin == end‬‬

‫שימו לב שבאלגורית יש שני זימוני רקורסיביי‪ .‬התהלי‪ $‬יהיה דומה לזה שראינו בחישוב‬
‫מספרי פיבונצ'י )תהלי‪" $‬הלו‪$‬חזור" לא רצי((‪ ,‬ואנו נפרוס אותו במבנה של ע‪ -‬מעקב‪.‬‬

‫}‪.arr = {4 ,6 ,9 ,7 ,1 ,5 ,8‬‬ ‫דוגמה‪ :‬נעקוב חלקית אחרי הפעולה )…(‪ findMax‬לגבי אותו מער‪:$‬‬
‫כיוו שאור‪ $‬המער‪ $‬הוא ‪ ,7‬והאינדקס של האיבר האחרו הוא ‪ ,6‬יהיה הזימו הראשו ע‬
‫הערכי ‪:begin = 0, end = 6‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 98‬‬

‫? השלימו את הציור ובדקו שאכ הער‪ $‬המוחזר מהרקורסיה הוא הער‪.9 $‬‬

‫לידס‬
‫ו‪ .‬דוגמה‪ :‬האלגורית של א( ְק ֵ‬
‫אחד האלגוריתמי המפורסמי במתמטיקה הומצא על ידי אוקלידס‪ ,‬מתמטיקאי יווני מפורס‬
‫שחי במאה השלישית לפני הספירה‪ .‬האלגורית מוצא את המחלק המשות‪ $‬הגדול ביותר של שני‬
‫מספרי שלמי חיוביי )שימו לב שתמיד יש כזה‪ ,‬ג א הוא רק המספר ‪ .(1‬בתיאור להל אנו‬
‫מניחי שהמספרי שבה אנו עוסקי ה שלמי וחיוביי‪ .‬המחלק המשות( המקסימלי של‬
‫שני מספרי‪ ,‬הוא המספר הגדול ביותר המחלק את שניה‪ .‬לדוגמה‪ :‬המחלק המשות(‬
‫המקסימלי עבור שני המספרי ‪ 42‬ו‪ ,28‬הוא ‪ ,14‬כיוו שזהו המספר הגדול ביותר המחלק את שני‬
‫המספרי‪.‬‬

‫האלגורית מתבסס על העובדה‪ ,‬כי א מספר מחלק שני מספרי‪ ,‬אזי הוא מחלק ג את‬
‫השארית של חלוקת הגדול שביניה במשנהו‪ .‬למשל‪ 3 ,‬מחלק את ‪ 24‬וג את ‪ ,18‬וכ את ‪ 6‬שהוא‬
‫השארית של חלוקת ‪ 24‬ב‪ .18‬עובדה זו נכונה בפרט למחלק המשות( המקסימלי‪ .‬מכא שכדי‬
‫למצוא מחלק משות( מקסימלי של ‪ 24‬ו‪ ,18‬די למצוא את המחלק המשות( המקסימלי של ‪18‬‬
‫ושל ‪ .6‬כאשר נחלק את ‪ 18‬ב‪ ,6‬נקבל שארית ‪ ,0‬כלומר ‪ 6‬מחלק את ‪ .18‬מכא ש‪ 6‬הוא המחלק‬
‫המשות( המקסימלי של ‪ 18‬ו‪ ,6‬ולכ ג של ‪ 24‬ו‪ .18‬באופ כללי נאמר שכדי למצוא מחלק‬
‫משות( מקסימלי של שני מספרי‪ ,‬די למצוא את המחלק המשות( המקסימלי של הקט מה‪,‬‬
‫ושל שארית חלוקת הגדול בקט‪ ,‬א שארית זו אינה ‪ .0‬א היא ‪ ,0‬אזי הקט שבה הוא המחלק‬
‫ ‪ 99‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫המשות( המקסימלי‪ .‬החלפת זוג המספרי הנתו בזוג חדש‪ ,‬שהוא "פשוט יותר" )כיוו שהמספר‬
‫הגדול הוחל( בשארית החלוקה שלו‪ ,‬שהוא מספר קט יותר(‪ ,‬מובילה לאלגורית רקורסיבי‪.‬‬
‫האלגורית הרקורסיבי מקבל שני מספרי חיוביי ‪ ,k ,j‬ומחזיר את המחלק המשות(‬
‫המקסימלי שלה‪ .‬האלגורית הרקורסיבי מגיע לתוצאת החישוב במהירות מפתיעה‪.‬‬

‫? מהו תנאי העצירה של האלגורית של אוקלידס?‬

‫ההנחה המקדימה של האלגורית של אוקלידס היא‪:k ≥ 0 ,j > 0 :‬‬


‫)‪public static int euclidGcd(int j, int k‬‬
‫{‬
‫)‪if (k == 0‬‬
‫;‪return j‬‬
‫‪else‬‬
‫;)‪return euclidGcd(k, j%k‬‬
‫}‬

‫להל פירוט שלבי הרקורסיה עבור קלט של שני מספרי גדולי למדי )‪:(j=1071, k=1029‬‬

‫ער‬ ‫שארית‬
‫החזרה‬ ‫מחליפי ‬ ‫החלוקה‬ ‫חלוקה‬ ‫‪j‬‬ ‫‪k‬‬

‫‪j  1029‬‬
‫‪42‬‬ ‫‪1071/1029‬‬ ‫‪1071‬‬ ‫‪1029‬‬
‫‪k‬‬ ‫‪42‬‬

‫‪j‬‬ ‫‪42‬‬
‫‪21‬‬ ‫‪1029/42‬‬ ‫‪1029‬‬ ‫‪42‬‬
‫‪k‬‬ ‫‪21‬‬

‫‪j‬‬ ‫‪21‬‬
‫‪21‬‬ ‫‪0‬‬ ‫‪42/21‬‬ ‫‪42‬‬ ‫‪21‬‬
‫‪k‬‬ ‫‪0‬‬

‫האלגורית של אוקלידס פשוט ואלגנטי‪ ,‬ומפתיע במהירות שבה הוא מגיע לתוצאה‪ ,‬ג עבור‬
‫מספרי גדולי יחסית‪ .‬נסו לדמיי כמה מסוב‪ $‬היה חישוב "רגיל" של המכנה המשות( הגדול‬
‫ביותר‪ ,‬שהרי חישוב כזה מבצע לולאה בי המספרי ‪ 1‬עד ‪) k‬המספר הקט מבי השניי( ובודק‬
‫הא ‪ j‬ו ‪ k‬מתחלקי בכל אחד מהמספרי האלה‪ .‬במקרה שלנו הלולאה הייתה מתבצעת יותר‬
‫מ‪ 1000‬פעמי!‬

‫זוהי דוגמה מובהקת לכוחה הרב של הרקורסיה‪ ,‬המאפשרת לעתי למצוא פתרונות אלגנטיי‪,‬‬
‫פשוטי ויעילי לבעיות הנחשבות "קשות"‪ .‬לעתי קרובות‪ ,‬אחרי שנמצא פתרו כזה‪ ,‬אפשר‬
‫למצוא ג פתרו איטרטיבי המבוסס על אותו רעיו‪ .‬לפעמי‪ ,‬ג הפתרו האיטרטיבי יהיה‬
‫אלגנטי ופשוט‪ ,‬א‪ $‬בלא מעט מקרי הוא יהיה מסוב‪ $‬יותר מהפתרו הרקורסיבי )ג א‬
‫יעילות זהה(‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 100‬‬

‫ז‪ .‬פעולת עזר רקורסיבית‬


‫כדי להבי את הצור‪ $‬בפעולות עזר רקורסיביות נתבונ בדוגמה שראינו בפרק על מציאת‬
‫מקסימו במער‪) $‬ה‪ .(2.‬הכותרת של הפעולה היא‪:‬‬
‫)‪public static int findMax(int[] arr, int begin, int end‬‬

‫והקריאות הרקורסיביות מצמצמות את טווח החיפוש באמצעות האינדקסי ‪ begin‬ו‪.end‬‬

‫א‪ $‬ישנה אי נוחות מסוימת מבחינת המשתמש בקריאה מסוג זה‪ .‬המשתמש רוצה לדעת מהו‬
‫המקסימו במער‪ $‬מסוי‪ .‬ברור לו כי הוא צרי‪ $‬להעביר את המער‪ $‬כפרמטר‪ .‬א‪ $‬הדרישה‬
‫להעביר ה את תחילת המער‪ $‬וה את סופו אינה מובנת )יתרה מזאת‪ ,‬הרי בקריאה שהמשתמש‬
‫מבצע ‪ ,begin=0‬ו ‪ ,end=arr.length-1‬לכ ההכנסה שלה כפרמטרי נראית מיותרת לחלוטי(‪.‬‬

‫לעתי קרובות נרצה לעטו( את הפעולה הרקורסיבית בפעולה נוספת אשר מסתירה פרטי‬
‫טכניי מסוג זה‪.‬‬

‫נכתוב את פעולת ה"מעטפת"‪:‬‬

‫)‪public static int findMax(int[] arr‬‬


‫{‬
‫;)‪return findMax(arr, 0, arr.length-1‬‬
‫}‬

‫פעולת ה"מעטפת" מבצעת את הקריאה לפעולה הרקורסיבית‪ .‬נגדיר את הפעולה הרקורסיבית‬


‫כפעולה פרטית‪ ,‬כיוו שהמשתמש אינו אמור להכיר אותה או להשתמש בה ישירות‪:‬‬
‫)‪private static int findMaxHelp(int[] arr, int begin, int end‬‬
‫{‬
‫‪...‬‬
‫}‬

‫בצורה זו המשתמש מזמ את הפעולה בצורה נוחה והגיונית‪:‬‬


‫;)‪findMax(arr‬‬

‫ואינו שולח את האינדקסי כפרמטרי‪.‬‬

‫מוב שג א משתמשי בפתרו הרקורסיבי הראשו לבעיית החיפוש במער‪ $‬נוצרת אותה‬
‫הבעיה‪ ,‬משו שאנו נדרשי להעביר ג ער‪ $‬של אינדקס אחד‪ .‬ג כא הפתרו המוצע הוא‬
‫להשתמש בכותרת של פעולת חיפוש שאינה מציינת את האינדקס במער‪ $‬שבו החיפוש מתחיל‬
‫אלא מעבירה את המער‪ $‬עצמו בלבד‪ .‬בגו( הפעולה יהיה זימו לפעולת עזר פרטית ובי‬
‫הפרמטרי שלה יהיה כלול ג האינדקס הנדרש‪.‬‬
‫ ‪ 101‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫ח‪ .‬סיכו ‬
‫אלגורית רקורסיבי הוא אלגורית שמשמש לפתרו בעיות שאינ פשוטות דיי לפתרו ישיר‪.‬‬
‫האלגורית הרקורסיבי מפעיל את עצמו פע אחת או יותר‪ ,‬על מופעי אחרי של אותה בעיה‪.‬‬
‫כדי שהגדרה רקורסיבית של אלגורית )פתרו( תהיה יעילה‪ ,‬חשוב שמופעי הבעיה הנפתרי‬
‫בהפעלות הרקורסיביות יהיו "קטני יותר" או "פשוטי יותר" מהמופע המקורי‪ .‬תנאי זה מבטיח‬
‫שתהליכי הפירוק מגיעי בסופו של דבר לבעיות פשוטות שאות נית לפתור ישירות‪.‬‬

‫רקורסיה היא הגדרה של מושג או אלגורית )כלומר של פתרו לבעיה(‪ ,‬המשתמשת במושג‬ ‫•‬
‫או באלגורית המוגדר‪ .‬אלגורית רקורסיבי יזמ את עצמו שוב ושוב על ערכי פרמטרי‬
‫הולכי וקטני‪ ,‬עד שיגיע למקרה הבסיסי שהפתרו שלו מתקבל באופ ישיר‪.‬‬
‫כשכותבי פתרו רקורסיבי לבעיה יש להגדיר שני מצבי‪ :‬מקרה מורכב שאותו נפרק‬ ‫•‬
‫למקרי פשוטי יותר‪ ,‬ומקרה בסיסי‪ ,‬כלומר תנאי עצירה‪ ,‬המגדיר את הפתרו למקרה‬
‫הפשוט בלי זימו נוס( של אלגורית הפתרו‪.‬‬
‫קיימי סוגי רקורסיה רבי‪ .‬בפרק זה התייחסנו במפורט לארבעה‪" :‬רקורסיית זנב"‬ ‫•‬
‫המסתיימת ע פתרו המקרה הבסיסי; "רקורסיית הלו‪$‬חזור" שבה לאחר פתרו המקרה‬
‫הבסיסי יש לחזור ע הער‪ $‬המחושב ולבצע חישובי קודמי שממתיני לסיומ;‬
‫"רקורסיה כפולה" המזמנת את ההלי‪ $‬הרקורסיבי פעמיי בכל שלב; "רקורסיה הדדית"‪,‬‬
‫המתבצעת באמצעות שתי פעולות שונות )לפחות(‪.‬‬
‫באלגוריתמי על מערכי‪ ,‬הדר‪ $‬הטיפוסית לקבלת פתרו רקורסיבי היא על ידי הגדרת‬ ‫•‬
‫הבעיה מחדש‪ ,‬כ‪ $‬שתתייחס לקטעי של מערכי‪ .‬כ‪ $‬מתקבלי מקרי פשוטי יותר על ידי‬
‫צמצו קטעי המער‪ $‬רק לחלקי שעליה האלגורית פועל‪.‬‬

‫מושגי ‬

‫‪recursive definition‬‬ ‫הגדרה רקורסיבית‬

‫‪base case‬‬ ‫מקרה בסיסי או תנאי עצירה‬

‫מקרה מורכב‬

‫‪recursion‬‬ ‫רקורסיה‬

‫רקורסיה הדדית‬

‫‪double recursion‬‬ ‫רקורסיה כפולה‬

‫רקורסיית הלו‪$‬חזור‬

‫‪tail recursion‬‬ ‫רקורסיית זנב )או‪ :‬רקורסיית הלו‪($‬‬


‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 102‬‬

‫תרגילי‬
‫א‪ .‬מעקב אחר רקורסיות‬
‫לפניכ כמה פעולות רקורסיביות‪ .‬עבור כל אחת מה רשמו את טענת היציאה‪.‬‬

‫‪ .1‬טענת כניסה‪ :‬הפעולה מקבלת תו ומספר של אישלילי‪.‬‬


‫)‪public static void mystery(char ch, int n‬‬
‫{‬
‫)‪if (n > 0‬‬
‫{‬
‫;)‪System.out.print(ch‬‬
‫;)‪mystery(ch, n-1‬‬
‫}‬
‫}‬

‫‪ .2‬טענת כניסה‪ :‬הפעולה מקבלת שני מספרי שלמי חיוביי‪.‬‬


‫)‪public static int mystery(int a, int b‬‬
‫{‬
‫)‪if (a < b‬‬
‫;)‪return (0‬‬
‫‪else‬‬
‫;))‪return (1 + mystery(a-b, b‬‬
‫}‬

‫‪ .3‬טענת כניסה‪ :‬הפעולה מקבלת מספר של חיובי‪.‬‬

‫)‪public static int mystery(int n‬‬


‫{‬
‫)‪if (n < 10‬‬
‫;)‪return (n‬‬
‫‪else‬‬
‫{‬
‫;‪int x = n % 10‬‬
‫;)‪int y = mystery(n / 10‬‬
‫)‪if (x > y‬‬
‫;‪return x‬‬
‫‪else‬‬
‫;‪return y‬‬
‫}‬
‫}‬
 103  ‫ – רקורסיה‬4 ‫פרק‬

.‫ הפעולה מקבלת מספר של חיובי‬:‫ טענת כניסה‬.4

public static int mystery(int num)


{
if (num == 1)
return (1);
else
return (mystery(num - 1) + 2 * num – 1);
}

‫ ומספר נוס( של‬,‫ של מספרי שלמי שאינו ריק‬$‫ הפעולה מקבלת מער‬:‫ טענת כניסה‬.5
‫וחיובי‬
.$‫הקט או שווה לגודל המער‬

public static float mystery(int[] a, int k)


{
float x;

if (k == 1)
return (a[0]);
x = mystery(a, k-1) * (k-1);
return ((a[k-1] + x) / k);
}

.‫ הפעולה מקבלת מספר של חיובי‬:‫ טענת כניסה‬.6

public static int mystery(int num)


{
if (num < 10)
return (num);
else
{
int i = 10;
while (num % i != num)
i *= 10;
return ((num % 10) * i / 10) + mystery(num / 10);
}
}

.‫ המספר השני הוא ג אישלילי‬,‫ הפעולה מקבלת שני מספרי שלמי‬:‫ טענת כניסה‬.7

public static int mystery(int a, int b)


{
if (b == 0)
return 0;
if (b % 2 == 0)
return mystery(a + a, b / 2);
return mystery(a + a, b / 2) + a;
}
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 104‬‬

‫לפניכ כמה פעולות רקורסיביות ובצמוד לכל אחת טענת הכניסה שלה וטענת היציאה שלה‪.‬‬
‫בכל פעולה חסרי ביטויי אחדי שעליכ להשלי כדי שהפעולה תבצע את הנדרש‪.‬‬
‫‪.8‬‬
‫טענת כניסה‪ :‬הפעולה מקבלת מספר של וחיובי‪.‬‬
‫טענת יציאה‪ :‬הפעולה מחזירה את סכו הספרות של המספר המתקבל‪.‬‬
‫)‪public static int sumDigits(int num‬‬
‫{‬
‫)___________( ‪if‬‬
‫;)‪return (num‬‬
‫;))___________(‪return (num % 10 + sumDigits‬‬
‫}‬

‫‪.9‬‬
‫טענת כניסה‪ :‬הפעולה מקבלת מספר של וחיובי‪.‬‬
‫טענת יציאה‪ :‬הפעולה מחזירה את מספר הספרות של המספר המתקבל‪.‬‬
‫)‪public static int numDigits(int num‬‬
‫{‬
‫)‪if (num < 10‬‬
‫;)___________( ‪return‬‬
‫;))___________(‪return (___________ + numDigits‬‬
‫}‬

‫‪.10‬‬
‫טענת כניסה‪ :‬הפעולה מקבלת מחרוזת‪.‬‬
‫טענת יציאה‪ :‬הפעולה מדפיסה את המחרוזת המתקבלת בסדר הפו‪.$‬‬
‫)‪public static void reverseString(String str‬‬
‫{‬
‫)‪if (str.length() > 0‬‬
‫{‬
‫;)___________(‪char ch = str.charAt‬‬
‫;)___________(‪reverseString‬‬
‫;)‪System.out.print(ch‬‬
‫}‬
‫}‬
‫ ‪ 105‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫‪.11‬‬
‫טענת כניסה‪ :‬הפעולה מקבלת מספר של חיובי וספרה‪.‬‬
‫טענת יציאה‪ :‬הפעולה מחזירה ‪ true‬א הספרה נמצאת במספר‪ ,‬אחרת מחזירה ‪. false‬‬
‫)‪public static boolean isDigitExist(int num, int digit‬‬
‫{‬
‫;‪boolean ans = num%10 == digit‬‬
‫)___________( ‪if‬‬
‫;)‪return (ans‬‬
‫;)___________( ‪return‬‬
‫}‬

‫‪.12‬‬
‫טענת כניסה‪ :‬הפעולה מקבלת מספר של חיובי‪.‬‬
‫טענת יציאה‪ :‬הפעולה מחזירה ‪ true‬א כל הספרות במספר זוגיות‪ ,‬אחרת מחזירה ‪. false‬‬
‫)‪public static boolean isAllDigitsEven(int num‬‬
‫{‬
‫;___________ = ‪boolean ans‬‬
‫)‪if (num < 10‬‬
‫;)___________( ‪return‬‬
‫;))___________(‪return (ans && isAllDigitsEven‬‬
‫}‬

‫בכל אחת מהשאלות הבאות עליכ לבחור פעולה רקורסיבית אחת מהשלוש המוצעות‪ ,‬כ‬
‫שתתאי לטענת הכניסה והיציאה המוצגות‪.‬‬

‫‪.13‬‬
‫טענת כניסה‪ :‬הפעולה מקבלת שני מספרי שלמי‪.n >= 0 ,m > 0 :‬‬
‫טענת יציאה‪ :‬הפעולה מחזירה ‪.n % m‬‬
‫א‪.‬‬
‫)‪public static int mod(int n, int m‬‬
‫{‬
‫)‪if (n > m‬‬
‫;‪return m‬‬
‫‪else‬‬
‫;)‪return mod(m-n, m‬‬
‫}‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬  106 

.‫ב‬
public static int mod(int n, int m)
{
if (m < n)
return 1;
else
return mod(n-m, m);
}

.‫ג‬
public static int mod(int n, int m)
{
if (n < m)
return n;
else
return mod(n-m, m);
}

.14
.‫ הפעולה מקבלת מחרוזת‬:‫טענת כניסה‬
.‫ הפעולה מחזירה מחרוזת הפוכה לזו שהתקבלה‬:‫טענת יציאה‬
.‫א‬
public static String rev(String str)
{
if (str.length() == 0)
return str;
else
return rev(str.substring(1)) + str.charAt(0);
}

.‫ב‬
public static String rev(String str)
{
if (str.length() == 0)
return str;
else
return rev(str.substring(0)) + str.charAt(0);
}

.‫ג‬
public static String rev(String str)
{
if (str.length() == 0)
return str;
else
return rev(str.substring(0)) + str.charAt(1);
}
 107  ‫ – רקורסיה‬4 ‫פרק‬

.‫ב הרקורסיבי – הריצו כל אחת מהתוכניות הבאות ובדקו מה היא מבצעת‬2ָ ‫ַה‬
:‫ תוכנית ראשונה‬.15
public class RecursiveTurtleDemo1
{
public static void main(String[] args)
{
Turtle t = new Turtle();
t.setDelay(50);
t.turnRight(90);
t.moveBackward(200);
t.tailDown();
t.setTailColor(Color.BLUE);
draw(t, 15, 4);
t.tailUp();
t.moveForward(40);
}
public static void draw(Turtle t, int width, int step)
{
if (step <= 1)
t.moveForward(width);
else
{
draw(t, width, step-1);
t.turnLeft(60);
draw(t, width, step-1);
t.turnRight(120);
draw(t, width, step-1);
t.turnLeft(60);
draw(t, width, step-1);
}
}
}

:‫ תוכנית שנייה‬.16
public class RecursiveTurtleDemo2
{
public static void main(String[] args)
{
Turtle t = new Turtle();
t.setDelay(50);
t.turnRight(90);
t.moveBackward(200);
t.tailDown();
t.setTailColor(Color.RED);
hilbert0(t, 4, 10);
t.tailUp();
t.moveForward(40);
}
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬  108 

public static void hilbert0(Turtle t, int n, int step)


{
if (n > 0)
{
t.turnRight(90);
hilbert1(t, n-1, step);
t.moveForward(step);
t.turnLeft(90);
hilbert0(t, n-1, step);
t.moveForward(step);
hilbert0(t, n-1, step);
t.turnLeft(90);
t.moveForward(step);
hilbert1(t, n-1, step);
t.turnRight(90);
}
}

public static void hilbert1(Turtle t, int n, int step)


{
if (n > 0)
{
t.turnLeft(90);
hilbert0(t, n-1, step);
t.moveForward(step);
t.turnRight(90);
hilbert1(t, n-1, step);
t.moveForward(step);
hilbert1(t, n-1, step);
t.turnRight(90);
t.moveForward(step);
hilbert0(t, n-1, step);
t.turnLeft(90);
}
}
}
‫ ‪ 109‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫ב‪ .‬כתיבת רקורסיות‬


‫לפני שתתחילו את כתיבת כל אחד מהאלגוריתמי‪ ,‬ענו על השאלות האלה‪:‬‬
‫א( ציינו את קלט הכניסה לפעולה הרקורסיבית‪ ,‬ומהו מקרה הבסיס )תנאי העצירה(‪.‬‬
‫ב( מה קורה במקרי קצה שוני? הא יש קלטי המחייבי התייחסות מיוחדת?‬
‫ג( הגדירו לעצמכ מהו סוג הרקורסיה שלפניכ )רקורסיית זנב‪ ,‬רקורסיית הלו‪$‬חזור‪,‬‬
‫רקורסיה הדדית(‪.‬‬
‫ד( ציינו מה צרי‪ $‬להתבצע בגו( הרקורסיה ומה היא מחזירה‪.‬‬

‫תרגילי על ספרות ומספרי ‪:‬‬


‫‪ .17‬כתבו פעולה רקורסיבית המקבלת מספר מטיפוס ‪ ,int‬ומחזירה את מספר ספרותיו‪.‬‬
‫‪ .18‬כתבו פעולה רקורסיבית המקבלת שני מספרי חיוביי ומחזירה את מספר הספרות הזהות‬
‫בערכ ובמיקומ בשני המספרי )כלומר עליכ לערו‪ $‬השוואה בי ספרת האחדות‪ ,‬ספרת‬
‫העשרות‪ ,‬ספרת המאות וכ הלאה(‪.‬‬
‫‪ .19‬כתבו פעולה רקורסיבית המקבלת מספר מטיפוס ‪ ,int‬ומחזירה את ממוצע ספרותיו‪.‬‬
‫‪ .20‬כתבו פעולה רקורסיבית המקבלת מספר חיובי מטיפוס ‪ int‬ומדפיסה אותו בבסיס בינרי‪.‬‬
‫‪ .21‬הפעולה ‪ numPrefix‬מקבלת מספר של שונה מאפס ומדפיסה את השורות האלה‪:‬‬
‫בשורה הראשונה יופיע המספר כולו‪.‬‬
‫בשורה הבאה – המספר ללא ספרת האחדות שלו‪.‬‬
‫בשורה שאחריה – המספר המקורי ללא ספרות העשרות והאחדות‪ ,‬וכ‪ $‬הלאה‪.‬‬
‫עד שבשורה האחרונה תודפס הספרה המשמעותית ביותר של המספר המקורי‪.‬‬

‫‪29807‬‬ ‫לדוגמה‪ ,‬עבור קלט‪ 29807 :‬יודפסו השורות הבאות‪:‬‬


‫‪2980‬‬
‫‪298‬‬
‫‪29‬‬
‫‪2‬‬
‫כתבו את הפעולה ‪.numPrefix‬‬

‫‪ .22‬כתבו אלגורית חשב חזקה )‪ ,(x, y‬המחשב את ‪ – xy‬תוצאת העלאת ‪ x‬בחזקת ‪ ,y‬כאשר שני‬
‫המספרי שלמי‪ y ,‬לא שלילי‪ x ,‬חיובי ממש‪ .‬ממשו את הפעולה‪.‬‬

‫תרגילי על מער‪:‬‬
‫‪ .23‬כתבו פעולה רקורסיבית המקבלת מער‪ $‬של מספרי ומחזירה את סכו איבריו‪.‬‬
‫‪ .24‬כתבו פעולה רקורסיבית הבודקת הא מער‪ $‬נתו ממוי בסדר עולה‪.‬‬
‫‪ .25‬כתבו פעולה רקורסיבית הבודקת הא ערכי המער‪ $‬מהווי סדרה חשבונית )כלומר קיי‬
‫הפרש קבוע בי איברי המער‪.($‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 110‬‬

‫תרגילי הדפסה‪:‬‬
‫‪ .26‬כתבו פעולה רקורסיבית המקבלת כפרמטר מספר טבעי ‪ n‬ומדפיסה משולש של כוכביות‪ ,‬ובו ‪n‬‬
‫שורות‪ .‬בשורה העליונה של המשולש יהיו ‪ n‬כוכביות צמודות‪ ,‬בשורה שמתחת יהיו ‪n-1‬‬
‫כוכביות צמודות‪ ,‬וכ‪ $‬הלאה‪ .‬בשורה התחתונה תודפס כוכבית אחת‪.‬‬
‫לדוגמה‪ ,‬עבור ‪ n = 4‬יודפס‪:‬‬
‫****‬
‫***‬
‫**‬
‫*‬
‫הגדירו מהו סוג הרקורסיה שאת מבצעי בפתרו שכתבת? הסבירו‪.‬‬

‫‪ .27‬כתבו פעולה רקורסיבית המקבלת כפרמטר מספר טבעי ‪ n‬ומדפיסה משולש של כוכביות‪ ,‬ובו‬
‫‪ n‬שורות‪ .‬בשורה העליונה של המשולש תהיה כוכבית אחת‪ ,‬בשורה השנייה שתי כוכביות‬
‫צמודות וכ‪ $‬הלאה‪ .‬בשורה האחרונה יודפסו ‪ n‬כוכביות צמודות‪.‬‬
‫לדוגמה‪ ,‬עבור ‪ n = 4‬יודפס‪:‬‬
‫*‬
‫**‬
‫***‬
‫****‬

‫‪ .28‬כתבו פעולה רקורסיבית המקבלת כפרמטר מספר טבעי ‪ ,n‬ומדפיסה צורה של שני משולשי‬
‫המחוברי בקודקוד‪ .‬בשורה הראשונה שלו ‪ n‬כוכביות ובשורה האחרונה שלו ‪ n‬כוכביות‪:‬‬
‫לדוגמה‪ ,‬עבור ‪:n = 3‬‬
‫***‬
‫**‬
‫*‬
‫**‬
‫***‬
‫ ‪ 111‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫ג‪ .‬תרגילי אתגר‬


‫‪ .29‬פרמוטציות‬
‫תמורה )פרמוטציה‪ (permutation ,‬של מער‪ $‬היא סידור כלשהו של איברי המער‪.$‬‬
‫כתבו פעולה רקורסיבית המדפיסה את כל התמורות של מער‪ $‬נתו‪.‬‬
‫לדוגמה‪ :‬עבור המער‪ $‬שמכיל את ‪8 ,4 ,2‬‬
‫יודפסו התמורות הבאות‪:‬‬

‫‪2 ,4 ,8‬‬
‫‪2 ,8 ,4‬‬
‫‪4 ,2 ,8‬‬
‫‪4 ,8 ,2‬‬
‫‪8 ,2 ,4‬‬
‫‪8 ,4 ,2‬‬

‫‪ .30‬חידת מגדלי האנוי )‪(Towers of Hanoi‬‬


‫חידת מגדלי האנוי היא חידה מתמטית שהומצאה על ידי המתמטיקאי הצרפתי אדוארד לוקאס‬
‫ב‪ ,1883‬והפכה למשחק חביב‪ .‬המשחק בנוי מלוח שבו נעוצי שלושה מוטות הממוספרי מ‪ 1‬עד‬
‫‪ 3‬ומדיסקיות בהיקפי שוני‪ .‬בתחילת המשחק‪ ,‬על מוט מספר ‪ 1‬מושחלות ‪ n‬דיסקיות‬
‫מהדיסקית שהיקפה גדול בסדר יורד )כמתואר באיור(‪ .‬הדיסקיות יוצרות צורה של מגדל ומכא‬
‫שמו של המשחק‪ .‬מטרת המשחק היא להעביר את מגדל הדסקיות בשלמותו ממוט ‪ 1‬למוט ‪ 3‬על פי‬
‫הכללי האלה‪:‬‬

‫א‪ .‬נית להעביר דיסקית אחת בלבד בכל שלב‪.‬‬

‫ב‪ .‬בא( שלב אסור שדיסקית גדולה תהיה מונחת על דיסקית קטנה ממנה‪.‬‬

‫כתבו פעולה רקורסיבית‪:‬‬


‫)‪public static void solveHanoi(int n‬‬
‫המקבלת מספר טבעי ‪ ,n‬ומדפיסה את סדרת ההזזות שיש לבצע על מנת להעביר את ‪ n‬הדיסקיות‬
‫ממוט מספר ‪ 1‬למוט מספר ‪ 3‬על פי כללי המשחק‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – ג'אווה‬ ‫ ‪ 112‬‬

‫למשל עבור הזימו )‪ ,solveHanoi (2‬כלומר משחק ב‪ 2‬דיסקיות‪ ,‬יתקבל פלט זה‪:‬‬
‫הזז מ‪ 1‬ל‪2‬‬
‫הזז מ‪ 1‬ל‪3‬‬
‫הזז מ‪ 2‬ל‪3‬‬
‫פירושה של הפקודה "הזז מ‪ a‬ל‪ "b‬היא הזזת הדיסקית העליונה ממוט מספר ‪ a‬למוט מספר ‪.b‬‬

‫רמז‪ :‬היעזרו בפעולת עזר רקורסיבית‪ ,‬אשר תקבל כפרמטר את מספרי המוטות ואת מספר‬
‫הדיסקיות הכולל‪:‬‬

‫)‪private static void solveHanoi(int a, int b, int c, int n‬‬


‫פעולה זו תעביר את ‪ n‬הטבעות ממוט מספר ‪ a‬למוט מספר ‪ c‬בעזרת מוט מספר ‪.b‬‬

‫‪ .31‬בעיית שמונה המלכות‪:‬‬


‫מלכה )‪ (Queen‬במשחק שחמט יכולה לנוע בשורה‪ ,‬בטור או באלכסו יחסית למקו שהיא‬
‫עומדת בו‪ .‬המלכה יכולה לנוע מספר בלתי מוגבל של משבצות בכיוו הנבחר‪ ,‬כל עוד כלי אחר אינו‬
‫חוס את דרכה‪ ,‬וכ כל עוד לא יצאה מהלוח‪ .‬מלכה יכולה להכות כל כלי העומד בדרכה א‪ $‬אחרי‬
‫שהכתה כלי )כלומר הוציאה אותו מהמשחק ונעמדה במקומו(‪ ,‬היא אינה יכולה לנוע יותר עד‬
‫לתור הבא‪ .‬מלכה מאיימת על כל הכלי הנמצאי בשורה‪ ,‬בעמודה או באלכסו של המשבצת בה‬
‫היא ניצבת )כלומר יכולה להוציא בתורה(‪.‬‬

‫כתבו פעולה רקורסיבית שתציב שמונה מלכות על לוח שחמט ריק )מער‪ $‬דוממדי בגודל ‪ ,(8x8‬כ‪$‬‬
‫שא( אחת לא תאיי על האחרות‪.‬‬

‫‪ .32‬בעיית מסעי פרש‬


‫פרש )‪ (knight‬במשחק שחמט )מער‪ $‬דוממדי בגודל ‪ (8x8‬העומד על משבצת שמיקומה‬
‫)‪ (row, col‬יכול להגיע לכל אחת מ‪ 8‬המשבצות )א קיימות(‪ ,‬כמתואר באיור זה‪:‬‬

‫‪8‬‬ ‫‪1‬‬
‫‪7‬‬ ‫‪2‬‬

‫ ‪row‬‬ ‫פרש‬

‫‪6‬‬ ‫‪3‬‬
‫‪5‬‬ ‫‪4‬‬

‫‬
‫‪col‬‬
‫מעבר הפרש למשבצת אפשרית נקרא מסע פרש )‪ (knight move‬המורכב ממעבר על שתי‬
‫משבצות רצופות בכיוו מאוז ואחת בכיוו מאונ‪ ,$‬או שתי משבצות רצופות בכיוו מאונ‪$‬‬
‫ואחת בכיוו מאוז‪ .‬שימו לב‪ ,‬כי א הפרש נמצא על אחת המשבצות שבמסגרת הלוח או‬
‫קרוב לה‪ ,‬הוא אינו יכול לבצע את כל ‪ 8‬המסעות!‬
‫ ‪ 113‬‬ ‫פרק ‪ – 4‬רקורסיה‬

‫כתבו פעולה רקורסיבית שתאפשר ביקור של הפרש בכל משבצות לוח השחמט‪ .‬הביקור‬
‫יתחיל במשבצת )‪ .(0,0‬בסו( התוכנית בכל משבצת בלוח יופיע מספר )מ‪ 1‬ועד ‪ (64‬המתאר את‬
‫מספר הביקור של הפרש במשבצת‪ .‬לדוגמה התבוננו בלוח מסעי הפרש שהתקבל‪:‬‬

‫‪ .33‬בעיית המבו‬
‫נתו מער‪ $‬דוממדי המדמה מבו‪ $‬שלו נקודת כניסה אחת ונקודת יציאה אחת‪ .‬יש לכתוב פעולה‬
‫רקורסיבית המדפיסה את המסלול ליציאה מהמבו‪.$‬‬

You might also like