330 likes | 387 Views
תרגול מס' 7: Memoization Quicksort תרגילים מתקדמים ברקורסיה. סדרת פיבונצ'י. לאונדרדו מפיזה הידוע בשמות ליאונדרו פיזאנו, ליאונרדו בונאצ'י, או בפשטות פיבונצ'י נחשב בעיני רבים לגדול המתמטיקאים של ימי הביניים. על שמו קרויה סדרת המספרים הבאה (בניגוד לדעה הרווחת הוא לא המציא אותה, רק השתמש בה):
E N D
תרגול מס' 7: MemoizationQuicksortתרגילים מתקדמים ברקורסיה
סדרת פיבונצ'י • לאונדרדו מפיזה הידוע בשמות ליאונדרו פיזאנו, ליאונרדו בונאצ'י, או בפשטות פיבונצ'י נחשב בעיני רבים לגדול המתמטיקאים של ימי הביניים. • על שמו קרויה סדרת המספרים הבאה (בניגוד לדעה הרווחת הוא לא המציא אותה, רק השתמש בה): • סדרה זו מופיעה בטבע בצורות מפתיעות: http://en.wikipedia.org/wiki/Fibonacci_number
חישוב המספר ה-n בסדרה: publicclass Fib { publicstaticint fib(int n){ int ans; if (n==0) ans = 0; elseif (n==1) ans = 1; else ans = fib(n-1)+fib(n-2); return ans; } publicstaticvoid main(String[] args){ int n = 20; System.out.println("fib("+n+") = "+fib(n)); } } רקורסית זנב? לא!
fib(2) fib(1) fib(0) fib(5) fib(4) fib(3) fib(3) fib(2) fib(2) fib(1) fib(1) fib(1) fib(0) fib(1) fib(0) Memoization (without ‘r’) ברישום הבא, מובא עץ הקריאות לפונקציה כאשר ניתן לראות כי ישנם מספר חישובים שמתבצעים יותר מפעם אחת (אלו המודגשים בצבע):
ניתן לחסוך את החישובים המיותרים ע"י שמירת תוצאות של חישובים שבוצעו: נשמור תוצאות ביניים ב lookup table-כך שחישוב יתבצע פעם אחת לכל היותר. לכל ערך שנרצה לחשב, נבדוק תחילה האם ערך זה חושב, כלומר, האם יש עבורו ערך מתאים ב lookup table. אם כן, נשתמש בערך זה. אחרת, נבצע את החישוב ונשמור את התוצאה בכניסה המתאימה בטבלה. הערכים נשמרים בטבלה לפי הקלט. כלומר, לכל קלט אפשרי יש כניסה מתאימה בטבלה.
דוגמא בכדי ליעל את fib נשתמש בטכניקת הmemoization לשמירת תוצאות החישובים שבוצעו. נראה תוכנית המחשבת את האיבר ה n-י בסדרת פיבונאצ'י, ומשתמשת בטכניקה זו לשמירת תוצאות של חישובים רקורסיביים. במקרה זה ה lookup table הינה מערך חד מימדי, כך שהתא בעל אינדקס i במערך, מכיל את הערך עבור fib(i). באם ערך זה עדיין לא חושב, יכיל תא זה את הערך 1-.
והפתרון: publicclass FibMemo{ publicstaticvoid main(String[] args) { int n = 20; System.out.println("fib("+n+") = “ + fib(n)); } publicstaticint fib(int n) { int[] lookupTable = newint[n+1]; for (int i=0; i < lookupTable.length ; i=i+1) lookupTable[i] = -1; //EMPTY return fib(lookupTable, n); } publicstaticint fib(int[] lookupTable, int n) { if (n==0) lookupTable[n]= 0; if (n==1) lookupTable[n]= 1; if (lookupTable[n]==-1)//EMPTY lookupTable[n] = fib (lookupTable,n-1) + fib(lookupTable,n-2); return lookupTable[n]; } }
fib(2) fib(1) fib(0) fib(5) fib(4) fib(3) fib(3) fib(2) fib(2) fib(1) fib(1) fib(1) fib(0) fib(1) fib(0) Memoization כעת עץ הקריאות לפונקציה יראה כך:
Divide-and-Conquer • טכניקת ה Divide-and-Conquer מבוססת על רעיון דומה לפתרון רקורסיבי של בעיות: • חלק את הבעיה המקורית לתתי בעיות קטנות (שניים או יותר) - Divide • פתור כל תת בעיה – Conquer • צרף את תתי הפתרונות לפתרון לבעיה המקורית. • ישנם אלגוריתמים רבים המתוכננים לפי עיקרון זה, עליהם נמנים האלגוריתמים הבאים: • Mergesort • Quicksort
מיון מהיר (Quick Sort) • חלוקת המערך לשני חלקים לפי ציר (pivot) שנבחר מחדש בכל שלב של הרקורסיה ומיון רקורסיבי של כל צד • הציר שנבחר הינו הערכה (ניחוש) של החציון של המספרים במערך • תזכורת: חציון הוא מדד למיקום המרכז של קבוצת נתונים מספריים. לדוגמא: החציון של קבוצת המספרים 1, 2, 22, 7, 19, 8, 16 הוא 8 • נבחר בקוד שלנו את האיבר הראשון כציר.
מיון מהיר – הקוד publicclass QuickSort { //…. publicstaticvoid quicksort(int[] arr){ quicksort(arr, 0, arr.length-1); } publicstaticvoid 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); } } //….
מיון מהיר – פונקציות עזר publicstaticvoid swap(int[] arr, int i, int j){ // swap arr[i] and arr[j] int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } publicstaticvoid 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); } } publicstaticvoid printArray (int[] arr) { for (int i=0; i<arr.length; i=i+1) System.out.print (arr[i]+" "); System.out.println(); }
מיון מהיר – partition publicstaticint partition(int[] arr, int start, int end){ int pivot = arr[start]; int i = start; int j = end; while(i<j){ while(i<end && arr[i] <= pivot) //scan upwards i=i+1; while(arr[j] > pivot) //scan downwards j=j-1; if (i<j) swap(arr,i,j); } swap(arr,start,j); //put pivot in place return j; }
דוגמאת ריצה של partition pivot pivot Swap! j i i i j j end i j start j j i
מיון מהיר – הקוד (המשך) import java.util.Scanner; publicclass QuickSort { publicstaticvoid main(String[] args){ Scanner sc = new Scanner(System.in); System.out.println("Enter number of elements to sort:"); int n = sc.nextInt(); int[] arr = newint[n]; // Initializes arr with random numbers // in [0..10*N) initRandomArray(arr); System.out.println("The input array:"); printArray(arr); quicksort(arr); System.out.println("The sorted array:"); printArray(arr); } //…. continued
Quicksort Example • Example: • http://www.cise.ufl.edu/~ddd/cis3020/summer-97/lectures/lec17/sld001.htm • www.cs.auckland.ac.nz/software/AlgAnim/Java/q_sort/tqs_new.html • Video: • www.youtube.com/watch?v=ywWBy6J5gz8 • http://www.youtube.com/watch?v=o2dm4X-t8L0 • http://www.youtube.com/watch?v=2HjspVV0jK4 • http://www.youtube.com/watch?v=FSyr8o8jjwM
הדפסת המחרוזות באורך n • בהינתן מספר n0 ≤, נרצה להחזיר מערך של 2n המחרוזות באורך n המורכבות מאפסים ואחדים.סדר המחרוזת הוא סדר המניה מאפס ל 2n-1 בבסיס 2. • הפלט עבור n=0: • {“”} • הפלט עבור n=1: • {“0”,”1”} • הפלט n=2: • {“00”,”01”,”10,”11”} • הפלט עבור n=3: • {“000”,”001”,”010”,”011”,“100”,”101”,”110”,”111”}
השלימו את הפונקציה publicstatic String[] binaryNums(int n){ String[] answer; if (n==0){ answer = new String[1]; answer[0]=""; } else { // // Your code here // } return answer; } 19
הרעיון של הרקורסיה n=3 n=2 n=1 n=0 “”
רעיון של הרקורסיה n=3 n=2 “0” + “” “1” + “” 0 0 1 n=1 n=0 “”
רעיון של הרקורסיה n=3 n=2 00 01 10 11 0 1 n=1 n=0 “”
רעיון של הרקורסיה n=3 000 001 010 011 100 101 110 111 n=2 00 01 10 11 0 1 n=1 n=0 “”
הקוד publicstatic 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; } 24
הדפסת הפרמוטציות של מחרוזת • פרמוטציה של מחרוזת מוגדרת כמחרוזת המכילה את אותן אותיות, ייתכן שבשינוי סדר. נניח בדוגמה זו שכל האותיות שונות זו מזו. • למשל הפרמוטציות עבור המחרוזת bcd הם: • “bcd" • “bdc" • “cbd“ • “cdb” • “dbc“ • “dcb”
הרעיון של הרקורסיה ““, “a” + “b” + “c” +”d” i=0 i=1 d, “a” + “b” + “c” output: abcd cd, “a” + “b” i=0 cd, “a” + “b” bcd, “a” i=0 bcd, “a” נרוץ עם i מ0 עד גודל המחרוזת ועבור כל i נבחן את כל המחרזות האפשריות בהן התו הi הוא הראשון כך שבכל קריאה i הוא האינדקס של האות הבאה אותה מצרפים למחרוזת i=0 abcd,”” abcd,””
הרעיון של הרקורסיה ““, “a” + “b” + “c” +”d” i=1 d, “a” + “b” + “c” output: abcd cd, “a” + “b” i=0 i=1 cd, “a” + “b” i=0 bcd, “a” bcd, “a” i=0 abcd,”” abcd,””
הרעיון של הרקורסיה ““, “a” + “b” + “c” +”d” d, “a” + “b” + “c” output: abcd i=1 i=2 cd, “a” + “b” cd, “a” + “b” “”, “a” + “b” + “d” + “c” i=0 c, “a” + “b” + “d” output: abdc bcd, “a” bcd, “a” i=0 abcd,”” abcd,””
הרעיון של הרקורסיה ““, “a” + “b” + “c” +”d” d, “a” + “b” + “c” output: abcd i=1 cd, “a” + “b” cd, “a” + “b” i=1 “”, “a” + “b” + “d” + “c” i=1 c, “a” + “b” + “d” output: abdc bcd, “a” bcd, “a” i=0 abcd,”” abcd,”” bd, “a” + “c” bd, “a” + “c” d, “a” + “c” + “b” output: acbd “”, “a” + “c” + “b” + “d”
הרעיון של הרקורסיה ““, “a” + “b” + “c” +”d” d, “a” + “b” + “c” output: abcd cd, “a” + “b” cd, “a” + “b” “”, “a” + “b” + “d” + “c” i=3 c, “a” + “b” + “d” output: abdc bcd, “a” bcd, “a” abcd,”” abcd,”” bd, “a” + “c” bd, “a” + “c” d, “a” + “c” + “b” . . . bc, “a” + “d” output: acbd “”, “a” + “c” + “b” + “d” . . .
קוד הדפסת הפרמוטציות של מחרוזת public static void perms(String s){ // We call the method perm(s,"") which prints // the empty string followed by each permutation // of s the empty string. perms(s,""); }
קוד הדפסת הפרמוטציות של מחרוזת /** Function prints all the permutation of a string. * Note: assume the string is a set (no duplicate * chars) */ // Prints string acc followed by all permutations of // string s1 publicstaticvoidperms(String s1, 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)); }
פונק' עזר: delete // This function returns the string s with the i-th // character removed publicstatic 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()); }