Μάθημα 20: Δυναμικός Προγραμματισμός (DP) Γενικά Είναι μια γενική μεθοδολογία και δεν υπάρχει ένα πρότυπο διατύπωσης /επίλυσης προβλημάτων Αρχικά ξεκίνησε σαν μαθηματική μέθοδος για τη λήψη σειράς αλληλοσυνδεόμενων αποφάσεων Εξελίχτηκε σε μέθοδο επίλυσης προβλημάτων με «επικαλυπτόμενα» (overlapping) υποπροβλήματα Το πρόβλημα υποδιαιρείται σε ένα αριθμό σταδίων (stages) Το αποτέλεσμα του ΔΠ είναι μια «πολιτική» η οποία προσδιορίζει τι πρέπει να γίνει σε κάθε στάδιο Η μέθοδος μπορεί να χρησιμοποιηθεί για γραμμικά καθώς και μη-γραμμικά προβλήματα. Επίσης για προβλήματα των οποίων οι παράμετροι είναι γνωστοί με βεβαιότητα Πολλές φορές η επίλυση προβλημάτων χρησιμοποιώντας αναδρομικές σχέσεις δεν είναι αποδοτική γιατί επιλύει το ίδιο πρόβλημα πολλές φορές: Παράδειγμα: Fibonacci Numbers long long int Fib1(long n){ if (n==0) return 0; if (n==1) return 1; return Fib1(n-1)+Fib1(n-2); Βασική ιδέα του ΔΠ είναι η χρήση της μνήμης για να αποθηκεύονται οι λύσεις των υποπροβλημάτων έτσι ώστε να μην είναι αναγκαία η επίλυση τους πολλαπλές φορές. Θυμηθείτε την λύση του προβλήματος χρησιμοποιώντας πίνακα η οποία είναι Ο(n)! long long int Fib2(long n){ F[0]=0; F[1]=1; for (long i=2; i<=n; i++) F[i]=F[i-1]+F[i-2]; return F[n]; Κλασσικά παραδείγματα DP 1. Μέγιστη Αύξουσα Υπακολουθία (Longest Increasing Subsequence - LIS) Έστω ότι σας δίνεται ο πίνακας A={-7, 10, 9, 2, 3, 8, 8, 1 να βρείτε τη μέγιστη αύξουσα υπακολουθία, όπως υποδηλώνει το όνομά της, η μεγαλύτερη δυνατή υπακολουθία αριθμών του Α, σε αύξουσα σειρά, όπου οι αριθμοί δεν είναι απαραίτητα συνεχόμενοι. Για την πιο πάνω ακολουθία η LIS είναι η {-7, 2, 3, 8 μεγέθους 4 Λύση: Έστω LIS (i) είναι η LIS για μετρητή i τότε: 1. LIS(0) = 1 2. LIS(i) = ans //υπολογίζουμε το ans όπως πιο κάτω: for (int i=0; i<n; i++){ ans = 1; for (int j=0; j<i; j++) if (arr[i]>arr[j]) Lesson 20 Page 1
ans = max(ans, 1 + lis[j]); lis[i]=ans; Το LIS(0) είναι 1, ο αριθμός -7 Το LIS(1) είναι τώρα 2, γιατί δημιουργούμε την ακολουθία {-7, 10 μεγέθους 2 Το LIS(2) είναι επίσης 2, γιατί δημιουργούμε την {-7, 9, αλλά όχι την {-7, 10 + {9 γιατί δεν είναι αύξουσα Το LIS(3) είναι επίσης 2,γιατί δημιουργούμε την {-7, 2. Οι {-7, 10 + {2 και {-7, 9 + {2 δεν είναι αύξουσες Το LIS(4) είναι 3, γιατί δημιουργούμε την {-7, 2 + {3 η μεγαλύτερη υπακολουθία μέχρι τώρα Το LIS(5) είναι 4, γιατί δημιουργούμε την {-7, 2, 3 + {8 η μεγαλύτερη υπακολουθία μέχρι τώρα Το LIS(6) είναι 4, γιατί δημιουργούμε την {-7, 2, 3 + {8 η μεγαλύτερη υπακολουθία μέχρι τώρα Το LIS(7) είναι 2, γιατί δημιουργούμε την {-7, 1 μεγέθους 2 Η απάντηση είναι τα στοιχεία LIS(5) και LIS(6) μεγέθους 4 2. Ρέστα (Coin change) Έστω ότι σας δίνεται ένα ποσό χρημάτων V και ένας αριθμός Ν από διαφορετικές αξίες νομισμάτων, να υπολογίσετε τον ελάχιστο αριθμό νομισμάτων που θα χρησιμοποιήσετε για να καλύψετε το ποσό V. Παράδειγμα 1: V = 10, N = 2, coinvalue = {1, 5 Μπορούμε να χρησιμοποιήσουμε: Α. Δέκα νομίσματα του 1 σεντ 10 Β. Ένα νόμισμα των 5 σεντ και 5 νομίσματα του ενός σεντ 6 Γ. Δύο νομίσματα των 5 σεντ 2 (Βέλτιστο) Μπορούμε να χρησιμοποιήσουμε και την άπληστη μέθοδο αν οι αξίες των νομισμάτων είναι οι κατάλληλες αλλά προτείνεται η χρήση DP για αποφυγή προβλημάτων όπως το παράδειγμα 2 πιο κάτω: Παράδειγμα 2: V = 7, N = 4, coinvalue = {1, 3, 4, 5 Η άπληστη μέθοδος θα μας δώσει 3, (5+1+1 = 7), αλλά η βέλτιστη λύση είναι 2, χρησιμοποιώντας 4+3 μόνο! Λύση για παράδειγμα 1: 1. coin(0) = 0 // 0 νομίσματα για να μας δώσουν 0 σεντ 2. coin(< 0) = εκτός ορίων 3. coin(value) = 1 +min(coin(value - coinvalue[i])) Η απάντηση θα είναι coin(v) for(int i=1; i<=v; i++){ int minvalue = MAXN; for(int j=0;j<n; j++){ if((i>=coinvalue[j])){ minvalue = min(minvalue, 1 + S[i-coinValue[j]]); S[i]=minValue; coin(0) = 0 coin(< 0) = coin(1) = 1, από το 1 + coin(1-1) = 0, επειδή το 1 + coin(1-5) είναι εκτός ορίων Lesson 20 Page 2
coin(2) = 2, από το 1 + coin(2-1) = 1, επειδή το 1 + coin(2-5) είναι εκτός ορίων coin(3) = 3, από το 1 + coin(3-1) = 2, επειδή το 1 + coin(3-5) είναι εκτός ορίων coin(4) = 4, από το 1 + coin(4-1) = 3, επειδή το 1 + coin(4-5) είναι εκτός ορίων coin(5) = 1, από το 1 + coin(5-5) = 1, που είναι μικρότερο από το 1 + coin(5-1) = 5 coin(6) = 2, από το 1 + coin(6-5) = 1 coin(7) = 3, από το 1 + coin(7-5) = 2 coin(8) = 4, από το 1 + coin(8-5) = 3 coin(9) = 5, από το 1 + coin(9-5) = 4 coin(10)= 2, από το 1 + coin(10-5) = 1 3. Μέγιστο Άθροισμα Υποπίνακα (Maximum Sum) Έστω ότι σας δίνεται πίνακας ακεραίων n x n (1 n 100), με τιμές στο πεδίο [-127,127], να βρείτε το μέγιστο άθροισμα που μπορεί να έχει ένας υποπίνακας. Παράδειγμα: Ο πιο κάτω πίνακας έχει ως μέγιστο άθροισμα το 15 όπως βλέπετε πιο κάτω: Λύση: Μια απλή (αλλά ΑΦΕΛΗΣ) λύση του είναι η πιο κάτω όμως πρόκειται για αλγόριθμο 100 6 Μια πιθανή λύση DP είναι να μετατρέψουμε τον n x n πίνακα σε n x n πίνακα αθροισμάτων όπου o πίνακας δεν θα κρατά πλέον τις αρχικές τιμές αλλά το άθροισμα των υποπινάκων του από (0, 0) μέχρι (i, j). Αυτό γίνεται σε χρόνο Ο(n 2 ) κατά την είσοδο των δεδομένων. Ο πιο πάνω κώδικας μετατρέπει τον αρχικό πίνακα στον πίνακα που βλέπετε στα δεξιά: Lesson 20 Page 3
0-2 -7 0 ==> 0-2 -9-9 9 2-6 2 ==> 9 9-4 -2-4 1-4 1 ==> 5 6-11 -8-1 8 0-2 ==> 4 13-4 -3 Με τον πίνακα αθροισμάτων μπορούμε να υπολογίσουμε τα αθροίσματα όλων των υποπινάκων από (i, j) σε (k, l) σε χρόνο Ο(1)! Έστω ότι θέλουμε να υπολογίσουμε το άθροισμα από το σημείο (1, 2) στο (3, 3). Χωρίζουμε τον πίνακα σε τέσσερα μέρη και υπολογίζουμε τα εξής: arr[3][3] - arr[0][3] - arr[3][1] + arr[0][1] = -3-13 - (-9) + (-2) = -9 Το πρόβλημα τώρα λύνεται σε χρόνο 100 4 4. Το πρόβλημα του σακιδίου (Knapsack) Δεδομένα: ένα σύνολο S από n στοιχεία, με κάθε στοιχείο έχω w ένα θετικό βάρος και b ένα θετικό όφελος Σκοπός: διαλέξτε στοιχεία με μέγιστο συνολικό όφελος αλλά με βάρος το πολύ W. Αλγόριθμος Knapsack Lesson 20 Page 4
Παράδειγμα 1 Item Weight Benefit Α 2 60 Β 3 75 C 4 90 Χωρητικότητα σακιδίου: 5 i / w 0 0 0 0 0 0 0 0 60 60 60 60 0 0 60 75 75 135 0 0 60 75 90 135 Αποτέλεσμα: Το μέγιστο βάρος του σακιδίου είναι 135 Παράδειγμα 2 Item Weight Benefit Α 1 60 Β 2 100 C 3 120 Χωρητικότητα σακιδίου: 5 i / w 0 0 0 0 0 0 0 60 60 60 60 60 0 60 100 160 160 160 0 60 100 160 180 220 Αποτέλεσμα: Το μέγιστο βάρος του σακιδίου είναι 220 Παράδειγμα 3 Item Weight Benefit Α 6 30 Β 3 14 C 4 16 Lesson 20 Page 5
D 2 9 Χωρητικότητα σακιδίου: 10 i / w 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 30 30 30 30 30 0 0 0 14 14 14 30 30 30 44 44 0 0 0 14 16 16 30 30 30 44 46 0 0 9 14 16 23 30 30 39 44 46 5. Καλαθόσφαιρα Πώς ένας παίκτης μπορεί να σημειώσει συνολικά Ν πόντους στην καλαθόσφαιρα; N=8 s i dp[i-s] dp[i] s= 1 i= 1 dp[0]= 1 dp[1]= 1 s= 1 i= 2 dp[1]= 1 dp[2]= 1 s= 1 i= 3 dp[2]= 1 dp[3]= 1 s= 1 i= 4 dp[3]= 1 dp[4]= 1 s= 1 i= 5 dp[4]= 1 dp[5]= 1 s= 1 i= 6 dp[5]= 1 dp[6]= 1 s= 1 i= 7 dp[6]= 1 dp[7]= 1 s= 1 i= 8 dp[7]= 1 dp[8]= 1 s= 2 i= 2 dp[0]= 1 dp[2]= 2 s= 2 i= 3 dp[1]= 1 dp[3]= 2 s= 2 i= 4 dp[2]= 2 dp[4]= 3 s= 2 i= 5 dp[3]= 2 dp[5]= 3 s= 2 i= 6 dp[4]= 3 dp[6]= 4 s= 2 i= 7 dp[5]= 3 dp[7]= 4 s= 2 i= 8 dp[6]= 4 dp[8]= 5 s= 3 i= 3 dp[0]= 1 dp[3]= 3 s= 3 i= 4 dp[1]= 1 dp[4]= 4 s= 3 i= 5 dp[2]= 2 dp[5]= 5 s= 3 i= 6 dp[3]= 3 dp[6]= 7 s= 3 i= 7 dp[4]= 4 dp[7]= 8 s= 3 i= 8 dp[5]= 5 dp[8]= 10 10 Lesson 20 Page 6