תרגול מס' :7,Memoization Quicksort ותרגילים מתקדמים ברקורסיה תרגול זה הוא התרגול האחרון שיהיה לכם בנושא רקורסיה והוא מכיל מגוון שאלות מתקדמות בנשוא זה. השאלות שיכלול תרגול זה: Memoization פיבונאצ'י Quicksort דוגמה: שאלה 5, בוחן 3002, סמסטר א. דוגמה: הדפסת הפרמוטציות של מחרוזת. דוגמה: הרכבת סכום נתון ממשקולות. דוגמה: שאלה 2 מועד א 3002. Memoization לאונדרדו מפיזה הידוע בשמות ליאונדרו פיזאנו, ליאונרדו בונאצ'י, או בפשטות פיבונצ'י נחשב בעיני רבים לגדול המתמטיקאים של ימי הביניים. על שמו קרויה סדרת המספרים הבאה )בניגוד לדעה הרווחת הוא לא המציא אותה, רק השתמש בה(: סדרה זו מופיעה בטבע בצורות מפתיעות: http://en.wikipedia.org/wiki/fibonacci_number public class Fib { public static int fib(int n){ int ans; if (n==0) ans = 0; else if (n==1) ans = 1; else ans = fib(n-1)+fib(n-2); return ans; public static void main(string[] args){ int n = 20; System.out.println("fib ("+n+") = "+fib(n));
ברישום הבא, מובא עץ הקריאות לפונקציה כאשר. n 5 ניתן לראות כי ישנם מספר חישובים שמתבצעים יותר מפעם אחת )אלו המודגשים בצבע(: fib(5) fib(4) fib(3) fib(3) fib(2) fib(2) fib(2) fib(0) fib(0) fib(0) ניתן לחסוך את החישובים המיותרים ע"י שמירת תוצאות של חישובים שבוצעו. :Memoization שמירה של תוצאות ביניים ב lookup tables כך שחישוב יתבצע פעם אחת לכל היותר. לכל ערך שנרצה לחשב, נבדוק תחילה האם ערך זה חושב, כלומר, האם יש עבורו ערך מתאים ב.lookup table אם כן, נשתמש בערך זה. אחרת, נבצע את החישוב ונשמור את התוצאה בכניסה המתאימה בטבלה. הערכים נשמרים בטבלה לפי ה.input כלומר, לכל קלט אפשרי יש כניסה מתאימה בטבלה. דוגמא 1: כבר ראינו פתרון רקורסיבי לחישוב האיבר ה n -י בסדרת פיבונאצ'י, וראינו כי פתרון זה אינו יעיל. הסיבה לחוסר היעילות היא חישובים שחוזרים על עצמם מספר פעמים. בכדי לייעל את הפתרון נשתמש בטכניקת ה memoization לשמירת תוצאות החישובים שבוצעו. נראה תוכנית המחשבת את האיבר ה n -י בסדרת פיבונאצ'י, אשר משתמשת בטכניקה זו ושומרת תוצאות של חישובים רקורסיביים. במקרה זה ה lookup table הינה מערך חד מימדי, כך שתא בעל אינדקס i במערך, מכיל את הערך. באם ערך זה עדיין לא חושב, יכיל תא זה את הערך 1-. f (i) public class FibMemo { public static void main(string[] args){ int n = 20; System.out.println("fibMemo("+n+") = "+fibmemo(n)); public static int fibmemo(int n){ int[] lookuptable = new int[n+1]; for (int i=0; i < lookuptable.length ; i=i+1) lookuptable[i] = -1; //EMPTY return fibmemo(lookuptable, n);
public static int fibmemo(int[] lookuptable, int n){ if (n==0) lookuptable[n] = 0; if (n==1) lookuptable[n] = 1; if (lookuptable[n] == -1) //EMPTY lookuptable[n] = fibmemo(lookuptable, n-1) + fibmemo(lookuptable,n-2); return lookuptable[n]; עבור n 20 מתבצעות 11,,11 קריאות לפונקציה הרגילה, לעומת 91 בלבד לפונקציה העושה שימוש ב- memoization. בעתיד נלמד כיצד לספור את מספר הקריאות לפונקציה בעזרת מונה. Divide-and-Conquer טכניקת ה Divide-and-Conquer מבוססת על רעיון דומה לפתרון רקורסיבי של בעיות: חלק את הבעיה המקורית לתתי בעיות קטנות )שניים או יותר( - Divide 1. פתור כל תת בעיה Conquer 1. צרף את תתי הפתרונות לפתרון לבעיה המקורית. 9. ישנם אלגוריתמים רבים המתוכננים לפי עיקרון זה, עליהם נמנים האלגוריתמים הבאים: Mergesort Quicksort Quicksort אלגוריתם למיון n איברים. בחר אלמנט ציר סדר את האיברים כך שכל s האיברים בחלקו השמאלי של המערך הינם איברים הקטנים או שווים לו. מה סדר את האיברים כך שכל n s האיברים בחלקו הימני של המערך הינם איברים הגדולים או שווים לו. מה עם האיבר הגדול ביותר בחלקו השמאלי של המערך החלף את ה מיין את שני תתי המערך בצורה רקורסיבית בדוגמא שלנו נניח כי ה הוא האיבר האחרון במערך הקלט. עבור {5,3,9,7,6 arr הרעיון הוא: ה הוא האיבר האחרון במערך הקלט חלקו השמאלי של המערך יכיל את האיברים שקטנים או שווים לו חלקו הימני יכיל את האיברים שגדולים או שווים לו
.partition נמצא במיקום הסופי שלו לאחר שלב ה p ה או בהכללה: [ i 1.. n] ו [ 0.. i 1] אח"כ יש שתי קריאות רקורסיביות עבור האינדקסים לאחר שלב ה.partition כאשר באנידקס i מיקמנו את ה [ i 1.. end ] ו [ start.. i 1] הקוד import java.util.scanner; public class QuickSort { public static void main(string[] args){ Scanner sc = new Scanner(System.in); System.out.println("Enter number of elements to sort:"); int n = sc.nextint(); int[] arr = new int[n]; initrandomarray(arr); // Initializes arr with random numbers in [0..10*N) System.out.println("The input array:"); printarray(arr); quicksort(arr); System.out.println("The sorted array:"); printarray(arr); public static void quicksort(int[] arr){ quicksort(arr, 0, arr.length-1); public static void quicksort(int[] arr, int start, int end){ if (start<end){ int i = partition(arr, start, end); quicksort(arr, start, i-1); quicksort(arr, i+1, end); public static int partition(int[] arr, int start, int end){ int = arr[start]; int i = start; int j = end; while(i<j){ while(i<end && arr[i] <= ) // scan upwards i=i+1; while(arr[j] > ) // scan downwards j=j-1; if (i<j) swap(arr,i,j); swap(arr,start,j); return j;
public static void swap(int[] arr, int i, int j){ // swap arr[i] and arr[j] int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; public static void initrandomarray(int[] arr){// shuffle the array arr int n = arr.length; for (int i = 0; i < n; i++) { arr[i] = (int) (Math.random() * 10 * n); public static void printarray (int[] arr) { for (int i=0; i<arr.length; i=i+1) { System.out.print (arr[i]+" "); System.out.println(); //class Quicksort דוגמה: בוחן 3002, סמסטר א. הפונקציה (n public static String[] binarynums(int מקבלת מספר n גדול או שווה לאפס )מותר להניח כי הקלט חוקי( ומחזירה מערך של 2 n המחרוזות באורך n המורכבות מאפסים ואחדים. סדר המחרוזת הוא סדר המניה מאפס ל 1- n 2 על בסיס 3 למשל: binarynums(0) יחזיר { binarynums(1) יחזיר { 0, 1 binarynums(2) יחזיר { 00, 01, 10, 11 binarynums(3) יחזיר 100, 101, 110, 111 { 000, 001, 010, 011, public static String[] binarynums(int n){ String[] answer; if (n==0){ answer = new String[1]; answer[0]=""; else { // // השלימו את הגדרת הפונקציה return answer;
תשובה: public static String[] binarynums(int n){ String[] answer; if (n==0){ answer = new String[1]; answer[0]=""; else { String[] prev = binarynums(n-1); answer = new String[2*prev.length]; for (int i =0;i<prev.length; i = i+1){ answer[i]= "0" + prev[i]; answer[prev.length + i] = "1" + prev[i]; return answer; דוגמה: הדפסת הפרמוטציות של מחרוזת פרמוטציה של מחרוזת מוגדרת כמחרוזת המכילה את אותן אותיות, ייתכן שבשינוי סדר. נניח בדוגמה זו שכל האותיות שונות זו מזו. למשל הפרמוטציות עבור המחרוזת bcd הם: bcd" bdc" cbd cdb dbc dcb public static void perms(string s){ // we call the method perm(s,"") which prints each permutation // of s followed by the empty string. perms(s,""); פתרון: /** this function prints all the permutation of a string. * Note: assume the string is a set (no duplicate chars) */ public static void perms(string s1, String acc){ // prints all permutations of string s1 followed by string acc if (s1.length()==0) System.out.println(acc); else for (int i=0; i<s1.length(); i=i+1) perms(delete(s1, i), acc +s1.charat(i)); // this function returns the string s with the i-th // character removed public static String delete(string s, int i){ // assumes that i is a position in the string return s.substring(0,i)+s.substring(i+1,s.length()); הפונקציה :delete
דוגמה )תזכורת מתרגול 6(: הרכבת סכום נתון ממשקולות בהנתן מערך משקולות ומשקל נוסף, נרצה לבדוק האם ניתן להרכיב מהמשקולות משקל השווה למשקל הנתון. דוגמא לקלט: weights={1,7,9,3 Sum = 12 במקרה זה הפונקציה תחזיר true כי ניתן לחבר את המשקולות 9 ו 2 ולקבל את הסכום 23. דוגמא לקלט: weights={1,7,9,3 Sum = 15 במקרה זה הפונקציה תחזיר false כי לא ניתן לחבר משקולות לקבלת הסכום 25. דרך פעולה קיימים שני מקרי בסיס: 2. הגענו לסכום הדרוש או במילים אחרות הסכום הנדרש הינו אפס 3. הגענו לסוף המערך עברנו על כל האיברים ולא מצאנו צירוף של איברים שסכומם שווה לסכום הנדרש נתבונן באיבר הראשון במערך. ייתכן שהוא ייבחר לקבוצת המשקולות שתרכיב את sum ויתכן שלא. אם הוא לא ייבחר אזי נותר לפתור בעיה קטנה יותר והיא האם ניתן להרכיב את הסכום weights[1..length [1- מבין המשקולות שבתאים sum אם הוא ייבחר אזי נותר לפתור בעיה קטנה יותר והיא האם ניתן להרכיב את הסכום. weights[1..length -1] מבין המשקולות שבתאים sum weights[0] וכנ"ל לגבי יתר האיברים בצורה רקורסיבית. s(int[], calcweight אשר weights,int i, פתרון זה קל נותר להציג כפונקציה רקורסיבית sum) int מקבלת בנוסף על sum ו weights פרמטר נוסף i ומחזירה האם ניתן להרכיב את הסכום sum מבין weights [ i.. קבוצת המשקולות שבתת המערך [ 1 length 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; עבור כל משקולת יש את האפשרות לבחור אותה לסכום או לא לבחור אותה. עובדה זו באה לידי ביטוי בקריאה הרקורסיבית.
דוגמה: מועד א 3006 השלימו את הגדרת הפונקציה ik) void subsetssum(int[] aset, int אשר מקבלת מערך aset של משקולות )ערכים שלמים חיוביים( ןמשקל שלם ik חיובי.)iK>0( הפונקציה תדפיס את כל תתי הקבוצות של איברי המערך aset שסכומם.iK לדוגמא אם {1,2,3,4,5 aset=,ik=10 הפונקציה תדפיס למסך: 1,2,3,4 1,4,5 2,3,5 הניחו כי המערך aset אינו null ושכל הערכים בו שלמים חיוביים וכי.iK>0 בנוסף, הניחו כי אין איבר המופיע במערך פעמיים. אין חשיבות לסדר האיברים בקבוצות המודפסות. בתבנית הנתונה ik) void subsetssum(int[] aset, int מתבצעת קריאה לפונקצית עזר,subsetsSum( ) בה ישנם מספר פרמטרים. השלימו את התבנית: public static void subsetssum(int[] aset, int ik){ ;( השלימו את החסר subsetssum( ( השלימו החסר ) subsetssum public static void ( השלימו את החסר סעיף א: סעיף ב: public static void subsetssum(int[] aset, int ik){ subsetssum(aset, ik,0, ); פתרון סעיף א: פתרון סעיף ב: public static void subsetssum(int[] aset, int ik, int index, String acc){ if(ik == 0) System.out.println(acc); else if (ik > 0 & index < aset.length){ subsetssum(aset, ik-aset[index], index+1, acc + aset[index] + ','); subsetssum(aset, ik, index+1, acc); { {