מבוא למחשב בשפת Matlab תרגול 10: רקורסיה מבוסס על שקפי הקורס "מבוא למדעי המחשב" ובסיוע שקפים של ערן אדן כל הזכויות שמורות לטכניון מכון טכנולוגי לישראל
תזכורת: פונקציות להלן קוד של פונקציה בשם :func function x = func(a) len = length(a); y = find(a > len); x = y+1; מה תהיה תוצאת הקריאה: res = func([17 25 3.5]) שימו לב שפונקציה יכולה לקרוא לפונקציה אחרת )למשל,)length, find ואז היא משהה את המשך ביצועה עד חזרת הפונקציה הנקראת. מבוא למטלאב לרפואנים. כל הזכויות שמורות 2
רקורסיה פשוטה ראינו שפונקציה יכולה לקרוא במהלך הקוד שלה לפונקציה אחרת. בפרט, ניסיון ראשון: פונקציה יכולה לקרוא לעצמה. פונקציה שלא מחזירה ערך function print1(str) fprintf('printing... %s\n',str); print1(str); מה יקרה עבור הקריאה?print1('dog') במה הקריאה שלעיל שונה מלולאת while אינסופית? רמז: בכל פעם שאנו קוראים לפונקציה, Matlab שומר בזכרונו את המצב האחרון בו היתה הסביבה הקוראת )מיקום בקוד, מצב המשתנים וכד'...(. מבוא למטלאב לרפואנים. כל הזכויות שמורות 3
ניסיון שני function print2(str) fprintf('printing... %s\n',str); print2(str(2:)); מה יקרה עכשיו עבור הקריאה?print2('dog') 4
ניסיון שלישי נוסיף תנאי שיעצור את הביצוע בשלב מסויים: function print3(str) if isempty(str) return fprintf('printing... %s\n',str); print3(str(2:)); מה יקרה עכשיו עבור הקריאה?print3('dog') התנאי שהוספנו נקרא תנאי עצירה. כל פונקציה רקורסיבית )פונקציה הקוראת לעצמה( חייבת להכיל תנאי עצירה על מנת לא להיות אינסופית. 5
סכימת הרקורסיה תנאי עצירה זהו מקרה פשוט של הבעיה בו הפתרון ידוע ללא קריאה רקורסיבית קריאה רקורסיבית עבור בעיה קטנה יותר נניח שהפונקציה יודעת לפתור נכון את הבעיה המוקטנת צעד הרקורסיה נפתור את הבעיה הגדולה ע"י שימוש בפתרון של הבעיה המוקטנת )שחושב בשלב הקודם( מבוא למטלאב לרפואנים. כל הזכויות שמורות 6
פונקציה רקורסיבית שמחזירה ערך function s = sumall(n) if N == 1 s = 1; s = N + sumall(n-1); נניח שהתבצעה הקריאה sumall(3) s. = מה יהיה ערכו של s? 7
שרשרת הקריאות עבור בעיה בגודל 3 function s = sumall(n) if N == 1 s = 1; s = N + sumall(n-1); הערך שחוזר למי שקרא ל-( sumall(3 הוא 6 sumall(3) 3 + 3 2 + 1 sumall(2) 1 sumall(1) באופן כללי, הפונקציה sumall מקבלת מספר טבעי N את סכום המספרים מ- 1 עד N, באופן רקורסיבי. ומחשבת 8
Recursion factorial example function res = myfactorial(x) % check: x is a non-negative integer if (x == 0 x == 1) res = 1; res = x * myfactorial(x-1); I don t know what is factorial of 3 But I know it is 3 multiply the factorial of 2 Ah ha! The factorial of 1 is 1! I don t know what is factorial of 2 But I know it is 2 multiply the factorial of 1 9
דוגמה function count_down(num) if (num < 0) return fprintf('%d\n', num); count_down(num-1); להלן הקוד של הפונקציה :count_down count_down(3) מה יקרה כשתתבצע הקריאה הבאה? מבוא למטלאב לרפואנים. כל הזכויות שמורות 10
חידה בשקף הקודם פקודת ההדפסה היתה לפני הקריאה הרקורסיבית. מה יקרה אם נחליף את הסדר בין הרקורסיבית כלהלן: פקודת ההדפסה והקריאה function count_down(num) if (num < 0) return count_down(num-1); fprintf('%d\n', num); count_down(3) בהנחה שהתבצעה הקריאה: מבוא למטלאב לרפואנים. כל הזכויות שמורות 11
תרגיל ממשו פונקציה רקורסיבית בשם multiply המקבלת שני מספרים טבעיים a ו- b ומחזירה את מכפלתם ללא שימוש בפעולת כפל. פתרון: תנאי העצירה הוא 1=b, צעד: a b = a (b-1) + a כיוון שאז.a b=a function res = multiply(a,b) if b==1 res = a; res = multiply(a,b-1) + a; מבוא למטלאב לרפואנים. כל הזכויות שמורות 12
תרגיל ממשו פונקציה רקורסיבית בשם isprefix המקבלת שתי מחרוזות p ו- t, ובודקת האם p היא רישא של t. אם כן מחזירה,true אחרת מחזירה.false לדוגמה: >> isprefix('his','history') ans = 1 >> isprefix('is','his') ans = 0 13
פתרון מחרוזת ריקה היא רישא של כל מחרוזת. לכן זה יהיה תנאי העצירה. בכל מקרה אחר, נשווה את האות הראשונה של המחרוזת p עם האות הראשונה של t, ואם הן שוות, נבדוק האם יתרת p היא רישא של יתרת t. function res = isprefix(p,t) if isempty(p) res = true; if p(1)==t(1) res = isprefix(p(2:), t(2:)); res = false; 14
תרגיל הגדרה: מחרוזת תיקרא פלינדרום אם קריאתה משמאל לימין שקולה לקריאתה מימין לשמאל. לדוגמה, המחרוזות הבאות הן פלינדרומים:,'abcba' '1221' והמחרוזות הבאות אינן פלינדרומים:,'abcde' '1212' כתבו פונקציה רקורסיבית המקבלת מחרוזת ומחזירה true אם המחרוזת היא פלינדרום ו- false אחרת. מבוא למטלאב לרפואנים. כל הזכויות שמורות 15
פתרון function res = ispalindrome(str) if length(str)<=1 res = true; if str(1)==str() res = ispalindrome(str(2:-1)); res = false; תנאי העצירה if length(str)==1 אינו טוב )למה?(, אבל length(str)==0 if יעבוד נכון מבוא למטלאב לרפואנים. כל הזכויות שמורות 16
תרגיל כתבו פונקציה רקורסיבית בשם f המקבלת מערך חד-מימדי של מספרים )כל האיברים במערך שלמים וחיוביים; כל איבר הוא מספר חד-ספרתי או מספר דו-ספרתי(, ומחזירה שני ערכים: מספר האיברים החד ספרתיים ומספר האיברים הדו-ספרתיים במערך. לדוגמה, >> [i,j] = f([21 3 4 28 9]) i = 3 j = 2 17
פתרון function [none, ntwo] = f(arr) if isempty(arr) none = 0; ntwo = 0; [none, ntwo] = f(arr(2:)); if arr(1)>=0 & arr(1)<= 9 % arr(1) has one digit none = none + 1; % arr(1) has two digits ntwo = ntwo + 1; 18
תרגיל מערך )של מספרים או תווים( נקרא פלינדרום אם קריאתו מימין לשמאל זהה לקריאתו משמאל לימין. נתונה פונקציה is_palin המקבלת מערך ובודקת האם הוא פלינדרום. כלומר אחרת הוא פלינדרום ו- false a אם המערך true מחזירה is_palin(a) )מימשנו בתרגול שעבר את הפונקציה הזו(. נתאר פונקציה חדשה בשם maxc_palin כלהלן: maxc_palin מקבלת מערך לא ריק a ומחזירה את אורך תת המערך הרציף המקסימלי המוכל ב- a שהוא פלינדרום. לדוגמה, הקריאה maxc_palin([13,13,4]) תחזיר 2 ואילו הקריאה maxc_palin([1,1,4,1,2,3]) תחזיר 3 ממשו את הפונקציה קיראו לפונקציה maxc_palin.is_palin באמצעות רקורסיה. לצורך כך 19
פתרון function res = maxc_palin(arr) if is_palin(arr) res = length(arr); a = maxc_palin(arr(1:-1)); b = maxc_palin(arr(2:)); res = max(a,b); פונקציה יכולה לקרוא לעצמה יותר מפעם אחת במהלך הקוד! 20
סיבוכיות זמן של הפונקציה maxc_palin עבור הפונקציה maxc_palin נספור את מספר הקריאות לפונקציה is_palin שהפונקציה מבצעת. נסמן ב-( T(n את מספר הקריאות ל- is_palin שמבצעת n. עבור מערך באורך maxc_palin T() 1 1 T( n) 2T ( n 1) 1 מתקיים: ניתן להוכיח באינדוקציה כי: קיים פתרון לא רקורסיבי יעיל יותר, באופן משמעותי, עבורו מתקיים: Tn ( ) n Tn ( ) n 2 1 2 n 2 21
מימוש לא רקורסיבי function result = maxc_palin(arr) len = length(arr); result = 1; for i=1:len-1 for j=i+1:len if is_palin(arr(i:j)) ; 22
מימוש לא רקורסיבי function result = maxc_palin(arr) len = length(arr); result = 1; for i=1:len-1 for j=i+1:len if is_palin(arr(i:j)) result = max(result,j-i+1) ; הפתרון האיטרטיבי הזה יעיל באופן משמעותי מהפתרון הרקורסיבי. 23
תרגיל פונקציית הספריה all מקבלת מערך מספרים ומחזירה איבריו שונים מאפס ו- false אחרת. אם כל true הפונקציה הרקורסיבית compare מקבלת שתי מחרוזות s1 ו- s2 המכילות רק אותיות קטנות ורווחים, ובודקת האם s1 ו- s2 שוות, כאשר מתעלמים מהרווחים. הפונקציה מחזירה true במקרה של שיוויון ו- false אחרת. דוגמאות למחרוזות שנחשבות שוות: ' hello w orld', 'h ell owor ld ' 'string', 's tr in g' דוגמאות למחרוזות שנחשבות שונות: 'helo world', 'hello world' 'string', 'strng' 24
תרגיל - המשך להלן מימוש רקורסיבי של compare החסר בקווים המסומנים. עם קטעים חסרים. השלימו את function reply = compare(s1,s2) if length(s1)==0 length(s2)==0 reply = all(s1==' ') & all(s2==' '); return; if s1(1) == ' ' reply = compare(, ); if s2(1) == ' ' reply = compare(, ); if s1(1) == s2(1) reply = compare(, ); reply = ; 25
פתרון להלן מימוש רקורסיבי של compare החסר בקווים המסומנים. עם קטעים חסרים. השלימו את function reply = compare(s1,s2) if length(s1)==0 length(s2)==0 reply = all(s1==' ') & all(s2==' '); return; if s1(1) == ' ' reply = compare( s1(2:),s2 ); if s2(1) == ' ' reply = compare( s1,s2(2:) ); if s1(1) == s2(1) reply = compare( s1(2:),s2(2:) ); reply = false ; 26
המשך התרגיל להלן מימוש לא רקורסיבי של הפונקציה,compare השלימו את החסר בקווים המסומנים. עם קטעים חסרים. function reply = compare1(s1,s2) ind1 = find(s1 ~= ' '); ind2 = find(s2 ~= ' '); if length(ind1)==length(ind2) reply = ; reply = ; 27
פתרון להלן מימוש לא רקורסיבי של הפונקציה,compare השלימו את החסר בקווים המסומנים. עם קטעים חסרים. function reply = compare1(s1,s2) ind1 = find(s1 ~= ' '); ind2 = find(s2 ~= ' '); if length(ind1)==length(ind2) reply = all(s1(ind1) == s2(ind2)) ; reply = false ; 28
סיכום במקרים רבים פונקציה רקורסיבית יכולה להיכתב באופן לא רקורסיבי בעזרת לולאה )וההיפך(. עבודה עם רקורסיה סובלת מבעייה מהותית: היא בזבזנית. רקורסיה ממומשת ע"י פונקציה שקוראת לעצמה. בזמן הריצה, בכל קריאה רקורסיבית נשמרת כתובת החזרה ואוסף המשתנים המקומיים של הפונקציה. מכאן שהרקורסיה דורשת זיכרון בגודל פרופורציונלי לעומקה. אז למה להשתמש ברקורסיה? מכיוון שרקורסיה היא לפעמים הדרך הנוחה ביותר לפתור בעיות מסויימות. דוגמאות:,maxc_palin מגדלי האנוי. 29