ΕΠΛ 22: Αλγόριθµοι και Πολυπλοκότητα Κατ οίκον Εργασία Σκελετοί Λύσεων. (α) Έστω δροµολόγηση e, e 2,, e των εργασιών, 2,,. Τότε οι χρόνοι συµπλήρωσης των εργασιών είναι e d e e 2 d e + d e 2 e d e + d e + d 2 e e d e + de + de +... + d 2 e Συνεπώς, για τη συγκεκριµένη δροµολόγηση έχουµε Ε = ( de + ( ) de + ( ) de +... + de ) 2 ( To κριτήριο αυτό είναι κατάλληλο για τη δηµιουργία άπληστου αλγορίθµου. Θα το αποδείξουµε µε τη µέθοδο της επαγωγής: Έστω δροµολόγηση = e, e 2,, e µια βέλτιστη λύση στο πρόβληµα, δηλαδή, µια δροµολόγηση που ελαχιστοποιεί το µέσο χρόνο συµπλήρωσης των εργασιών, 2,,. Έστω f η εργασία µε το µικρότερο χρόνο εκτέλεσης. Αν f = e, τότε η λύση περιέχει τη βέλτιστη επιλογή. ιαφορετικά, έστω η δροµολόγηση όπου η εργασίες f και e ανταλλάσσουν θέσεις, δηλαδή αν = e, e 2,, f,, e τότε = f, e 2,, e,, e. Έστω Ε και Ε οι µέσοι χρόνοι συµπλήρωσης των και, αντίστοιχα. Τότε E E ' = ( de + ( ) d ( )... ( )... ) e + d 2 e + + i d f + + de ( d f + ( ) de + ( ) d +... + ( ) +... + ) 2 e i d e d e = ( de d + ( )( )) f i d f de i = ( de d ) 0 f Το τελευταίο βήµα έπεται από την υπόθεση ότι η εργασία f έχει το µικρότερο χρόνο εκτέλεσης από όλες τις εργασίες, δηλαδή, de d f. Εποµένως, αφού, Ε Ε 0, τότε Ε Ε. Επιπλέον, αφού γνωρίζουµε ότι η είναι µια βέλτιστη λύση στο πρόβληµα, τότε Ε = Ε και συνεπώς η είναι µια βέλτιστη δροµολόγηση που ξεκινά µε την άπληστη επιλογή. Μπορούµε να χρησιµοποιήσουµε τα ίδια επιχειρήµατα και για την εργασία µε το δεύτερο µικρότερο χρόνο εκτέλεσης, και ούτω καθεξής (επαγωγή).
(i To κριτήριο αυτό είναι ακατάλληλο για το πρόβληµα. Αντιπαράδειγµα: = 2, d = και d 2 = 5. Επιλέγοντας σύµφωνα µε το κριτήριο, παίρνουµε τη δροµολόγηση 2,, µε c = 8, c 2 = 5 και Ε = /2. Υπάρχει όµως δροµολόγηση µε µικρότερο µέσο χρόνο συµπλήρωσης: η δροµολόγηση,2 µε c =, c 2 = 8 και Ε=/2. (β) Σε αυτή την παραλλαγή του προβλήµατος, υποθέτουµε ότι οι εργασίες δεν είναι διαθέσιµες από την αρχή αλλά αποδεσµεύονται σε κάποιους προκαθορισµένους χρόνους. Το κριτήριο απληστίας στην προκειµένη περίπτωση µεταβάλλεται ως εξής: Ανά πάσα στιγµή επέλεξε την εργασία µε το µικρότερο υπολειπόµενο χρόνο από αυτές που είναι διαθέσιµες. Αν κατά την εκτέλεση κάποιας εργασία αποδεσµευτεί άλλη εργασία µε µικρότερο χρόνο εκτέλεσης τότε διέκοψε την εργασία που τρέχει και εκτέλεσε τη νέα εργασία µε τον ελάχιστο χρόνο εκτέλεσης. Έστω r, r 2,, r k, οι χρόνοι αποδέσµευσης εργασιών σε αύξουσα σειρά, και έστω Σ, Σ 2,, Σ k, τα σύνολα εργασιών που αποδεσµεύονται σε κάθε ένα από αυτούς τους χρόνους, αντίστοιχα. Υποθέτουµε ότι κάθε ένα από τα σύνολα περιέχει τις εργασίες ταξινοµηµένες σε αύξουσα σειρά ως προς το χρόνο εκτέλεσής τους. Τότε, δυνατό στιγµιότυπο του προβλήµατος είναι το r = 0, Σ = {(e, ), (e 2, 4) r 2 =, Σ 2 = {(e, ), (e 4, 4) r =, Σ = {(e 5, ), (e, ) όπου ο αλγόριθµος θα έχει το πιο κάτω αποτέλεσµα: e e e e 5 e e e 2 e 4 0 2 4 5 7 8 9 0 2 4 5 7 Παρατηρείστε ότι η δροµολόγηση αυτή έχει µικρότερο µέσο χρόνο συµπλήρωσης από τη πιο κάτω δροµολόγηση (όπου δεν εφαρµόζεται διακοπή εργασιών): e e e 5 e e 2 e 4 0 2 4 5 7 8 9 0 2 4 5 7 Ακολουθεί ο αλγόριθµος σε ψευδοκώδικα. Υποθέτει την ύπαρξη δύο δοµών δεδοµένων: SStack Ταξινοµηµένη στοίβα: Λίστα ζευγών, ταξινοµηµένη σε αύξουσα σειρά ως προς το δεύτερο στοιχείο, που υποστηρίζεται από τις πράξεις: IsEmpty(S), (έλεγχος αν η S είναι κενή), Pop(S) (επιστροφή του κόµβου κορυφής), Push(S, x) (εισαγωγή του x στην κορυφή της S), Merge(S, T) (συγχώνευση των S και T) 2
Queue Ουρά: Λίστα ζευγών που υποστηρίζεται από τις πράξεις Equeue(Q,x) (εισαγωγή του x στο τέλος της Q) και Apped(Q, S) (εισαγωγή των στοιχείων της S µετά από τα στοιχεία της Q). it i = ; it time = r i ; SStack jobs = Σ i ; Queue schedule = ; while (!IsEmpty(jobs) OR i<=m) (i == m) Apped(schedule, jobs); else IsEmpty(jobs) i++; time = r i ; jobs = Σ i ; else (e,d) = Pop(jobs); ( d + time < r i+ ) Equeue(schedule, (e,d)); time = time + d; else (d >= r i+ time ) Equeue(schedule, (e, r i+ time); (d > r i+ time) Push(jobs, (e, d (r i+ time)); Merge(jobs, Σ i+ ); i++; time = r i ; Ο χρόνος εκτέλεσης της διαδικασίας είναι Ο( ΧΕ(Merge)) και εποµένως εξαρτάται από την υλοποίηση της δοµής SStack. Αν η υλοποίηση αυτή γίνει µε γραµµική δοµή δεδοµένων ο χρόνος εκτέλεσης της Merge (XE(Merge)) είναι της τάξης Ο() διαφορετικά, µε χρήση δενδρικής δοµής, είναι της τάξεως Ο( ). Η απόδειξη της ορθότητας αφήνεται ως άσκηση για τον αναγνώστη. 2. (α) Οι νόµιµες τοποθετήσεις είναι οι εξής: 2 4 5 7
(β) Αναδροµικός ορισµός της βέλτιστης λύσης. Έστω k) η µέγιστη τιµή τοποθέτησης από τη στήλη µέχρι τη στήλη i που τελειώνει µε στήλη στην τοποθέτηση υπ αριθµό k. Τότε το k) δίνεται αναδροµικά ως εξής: 0 gai(, + maxm {2,,4,7 i, gai(2, + maxm {,,4,5, i, gai(, + maxm {,2,4,,7 i, k) = gai(4, + maxm {,2,,5 i, gai(, + gai(, + maxm {2,4,7 i, gai(, + gai(4, + maxm {2, i, gai(2, + gai(4, + maxm {,,5 i, i = 0 k =, i > 0 k = 2, i > 0 k =, i > 0 k = 4, i > 0 k = 5, i > 0 k =, i > 0 k = 7, i > 0 Από αυτή την αναδροµική σχέση παρατηρούµε ότι για υπολογισµό κάθε k) απαιτείται γνώση κάποιων i-,k). Η βασική περίπτωση δίνεται από το 0,k) και το ζητούµενο είναι το max(,),,2),,,7)). Εποµένως χρησιµοποιούµε ένα πίνακα C διαστάσεων 7 και υπολογίζουµε τις θέσεις του ξεκινώντας µε τα C[0,k] και προχωρώντας προς τα C[, k], C[2,k],, και C[,k]. Σε ψευδοκώδικα ο αλγόριθµος έχει ως εξής: maxgai (it gai[4,]) for (k=; k<=7, k++) C[0,k]= 0; for (i = ; i <= ; i++){ max = C[i-,2]; (max < C[i-,]) max = C[i-,]; (max < C[i-,4]) max = C[i-,4]; (max < C[i-,7]) max = C[i-,7]; C[] = max + gai[]; max = C[i-,]; (max < C[i-,]) max = C[i-,]; (max < C[i-,4]) max = C[i-,4]; (max < C[i-,5]) max = C[i-,5]; (max < C[i-,]) max = C[i-,]; 4
C[2] = max + gai[2]; C[] = max + gai[]; C[4] = max + gai[4]; C[5] = max + gai[] + gai[]; C[] = max + gai[] + gai[4]; C[7] = max + gai[2] + gai[4]; max = C[,]); for (i= 2; i<=7; i++) (C[,i]> max) max = C[,i]; retur max; Ο χρόνος εκτέλεσης του αλγόριθµου είναι Ο( 7 c) = O(). (δ) Για να επιστρέψουµε την ακριβή τοποθέτηση µε τη µέγιστη τιµή, θα πρέπει να επεκτείνουµε τον αλγόριθµο έτσι ώστε να διατηρεί πληροφορίες για τη δοµή της βέλτιστης λύσης κάθε υποπροβλήµατος. Συγκεκριµένα, εκτός από τον πίνακα k) θα χρησιµοποιήσουµε ένα δεύτερο πίνακα Last(k) όπου θα φυλάγουµε τον αριθµό της τοποθέτησης της προτελευταίας στήλης στη βέλτιστη τοποθέτηση από τη στήλη µέχρι τη στήλη i που τελειώνει µε στήλη στην τοποθέτηση µε αριθµό k. Ο καινούριος αλγόριθµος φαίνεται πιο κάτω όπου οι προσθήκες εµφανίζονται µε σκούρα γράµµατα. maxgai (it gai[4,]) for (k=; k<=7, k++) C[0,k]= 0; D[0,k] = 0; for (i = ; i <= ; i++){ max = C[i-,2]; D[] = 2; (max < C[i-,]) max = C[i-,]; D[] = ; (max < C[i-,4]) max = C[i-,4]; D[] = 4; (max < C[i-,7]) max = C[i-,7]; D[] = 7; C[] = max + gai[]; max = C[i-,]; D[2] = ; (max < C[i-,]) 5
max = C[i-,]; D[] = ; (max < C[i-,4]) max = C[i-,4]; D[] = 4; (max < C[i-,5]) max = C[i-,5]; D[] = 5; (max < C[i-,]) max = C[i-,]; D[] = ; C[2] = max + gai[2]; C[] = max + gai[]; C[4] = max + gai[4]; max = C[,]); last = ; for (i= 2; i<=7; i++) (C[,i]> max) max = C[,i]; last = i; pritf(last); while (i > 0) last= D[last]; pritf(last); i--; Τυπώνει ανάποδα τη βέλτιστη τοποθέτηση. retur max;. (α) Από τον ορισµός του γινοµένου δύο πολυωνύµων, έχουµε ότι x) = c 0 + c x + c 2 x 2 + c x + c 4 x 4 όπου c 0 = a 0 b 0 c = a 0 b + a 0 b c 2 = a 0 b 2 + a b + a 0 b 2 c = a b 2 + a b 2 c 4 = a 2 b 2 Κατά συνέπεια, για υπολογισµό του γινοµένου απαιτούνται 9 πολλαπλασιασµοί και 4 προσθέσεις. (β) Χρησιµοποιώντας τα γινόµενα P,, P, το πρόβληµα ανάγεται στους πιο κάτω υπολογισµούς: c 0 = P c = P 4 P P 2 c 2 = P P P + P 2
c = P 5 P 2 P c 4 = P Αυτή η µέθοδος προφανώς απαιτεί τη χρήστη των γινοµένων και προσθαφαιρέσεων. (γ) Ο αλγόριθµος διαίρει και βασίλευε για το πρόβληµα έχει ως εξής: Έστω τα πολυώνυµα f(x) = a 0 + a x + + a - x -, g = b 0 + b x + + b - x -. Μοίρασε τα πολυώνυµα σε τρία τρίτα: f 0 (x)= a 0 ++a /- x /-, f (x)= a / ++a 2/- x /-, και f 2 (x)= a 2/ ++a - x /-, g 0 (x)= b 0 ++b /- x /-, g (x)= b / ++b 2/- x /-, και g 2 (x)= a 2/ ++b - x /-, Υπολόγισε αναδροµικά τα γινόµενα P = f 0 g 0 P 2 = f g P = f 2 g 2 P 4 = (f 0 + f ) (g 0 + g ) P 5 = (f + f 2 ) (g + g 2 ) P = (f 0 + f 2 ) (g 0 + g 2 ) Συνδύασε τα πιο πάνω γινόµενα για να λάβεις το γινόµενο πολυώνυµο ως εξής: f(x) g(x) = P + x / (P 4 P P 2 ) + x 2/ (P P P + P 2 ) + x (P 5 P 2 P ) + x 4/ P Έστω Τ() ο χρόνος εκτέλεσης της διαδικασίας σε πολυώνυµα βαθµού. Το Τ() δίνεται από την αναδροµική εξίσωση Τ() = Τ() = T(/) + c, Λύνοντας την αναδροµική εξίσωση µε τη µέθοδο της αντικατάσταση παίρνουµε: T() = T(/) + = 2 T(/ 2 ) + 2 + = T(/ ) + 2 2 + 2 + = i T(/ i ) + 2 i- + + 2 2 + 2 + Θέτοντας k = T() = k T(/ k ) + 2 k + + 2 2 + 2 + = k T(/) + (2 k + + 2 2 + 2 + ) = k + (2 k ) = k + k (2 k ) = 2 k k 7
= 2 = 2 = 2 ( = 2 Θ( ) ) (δ) Ο προτεινόµενος αλγόριθµος έχει χρόνο εκτέλεσης που δίνεται από την πιο κάτω αναδροµική εξίσωση: Τ() = m T(/4) + c Τ() = Χρησιµοποιώντας το θεώρηµα γενικής χρήσης συµπεραίνουµε ότι Aν O( lοg 4 m ε ), δηλαδή m>4, T() O( lοg 4 m ). Aν Θ( lοg 4 m ), δηλαδή m=4, T() O( lοg 4 4 lg ) = O( lg ). Aν Ω( lοg 4 m + ε ), δηλαδή m<4, T() O() Εποµένως, για m 4, δηλαδή στις δύο τελευταίες πιο πάνω περιπτώσεις, ο αλγόριθµος είναι αποδοτικότερος από τον αλγόριθµο που προτείνεται στο (γ) (, O( ( ). Επίσης, το ζητούµενο ισχύει (πρώτη περίπτωση) όταν m>4 και 4 m < ηλαδή 4 m < 4 < 4 m < 4 m 4 < Aρα m=9 είναι ο µεγαλύτερος ακέραιος που ικανοποιεί το ζητούµενο. m 4. Μοντελοποιούµε τη σκακιέρα ως ένα δισδιάστατο πίνακα Board[7,7] µε αρχική κατάσταση: Board[,] = Board[,2] = Board[,] = Board[,7] = INVALID Board[2,] = Board[2,2] = Board[2,] = Board[2,7] = INVALID Board[,] = Board[,2] = Board[,] = Board[,7] = INVALID Board[7,] = Board[7,2] = Board[7,] = Board[7,7] = INVALID Board[,] = Board[,4] = Board[,5] = FULL Board[2,] = Board[2,4] = Board[2,5] = FULL Board[,] = Board[,4] = Board[,5] = FULL Board[7,] = Board[7,4] = Board[7,5] = FULL Board[,] = Board[,2] = = Board[,7] = FULL Board[4,] = Board[4,2] = = Board[4,7] = FULL Board[5,] = Board[5,2] = = Board[5,7] = FULL Board[4,4] = EMPTY 9.59 Ορίζουµε µια κίνηση του παιχνιδιού ως µια τριάδα ( j 7, m {U, D, R, L, αν Board[j] = FULL, και επιπλέον ένα από τα ακόλουθα: m = U (up), Board[i, j] = FULL, Board[i 2, j] = EMPTY, 8
m = D (dow), Board[i +, j] = FULL, Board[i + 2, j] = EMPTY, m = R (right), Board[ j + ] = FULL, Board[i, j + 2] = EMPTY, m = L (left), Board[i, j ] = FULL, Board[ j 2] = EMPTY, Ονοµάζουµε µια ακολουθία από k νόµιµες κινήσεις του παιχνιδιού, 0 k, k- υποσχόµενη αν 0 k <, και εφαρµογή των k κινήσεων οδηγούν τη σκακιέρα σε κατάσταση όπου υπάρχουν δυνατές κινήσεις, ή k = και εφαρµογή των k κινήσεων οδηγούν τη σκακιέρα σε κατάσταση µε Board[4,4] = FULL. Λύση στο πρόβληµα είναι ένα -υποσχόµενο διάνυσµα. Θεωρώντας ότι ξεκινούµε µε το 0-υποσχόµενο διάνυσµα και µε δεδοµένο ένα i-υποσχόµενο διάνυσµα υπάρχουν τρεις περιπτώσεις είτε αυτό µπορεί να γίνει (i+)-υποσχόµενο διάνυσµα µε την προσθήκη µιας νέας κίνησης, είτε, αν i = 0, αυτό µπορεί να γίνει -υποσχόµενο διάνυσµα µε την προσθήκη µιας τελευταίας κίνησης, είτε έχουµε φτάσει σε αδιέξοδο, δηλαδή σε µια τοποθέτηση στην οποία είτε δεν υπάρχουν δυνατές κινήσεις, είτε δεν έχουµε φθάσει στην επιθυµητή τελική κατάσταση. Σε τέτοια περίπτωση ο αλγόριθµος θα οπισθοχωρήσει, θα µαταιώσει την τελευταία κίνηση επαναφέροντας τη σκακιέρα στην προηγούµενη κατάσταση, και θα θεωρήσει µια άλλη κίνηση από τη συγκεκριµένη κατάσταση, αν υπάρχει. Υποθέτουµε ότι έχουµε υλοποιηµένες τις διαδικασίες DoMove(move, Board) η οποία εκτελεί την κίνηση move στη σκακιέρα Board και µας επιστρέφει το σύνολο των νέων κινήσεων που έχουν γίνει εφικτές µετά από την κίνηση, και UdoMove(move, Board) η οποία µαταιώνει την κίνηση move στη σκακιέρα Board. Ο αλγόριθµος έχει ως εξής: board(){ i = 0; game = ; moves[2]= [,, ] moves[0] = (2,4,D),(4,2,R),(4,,L),(,4,U) ; while (i 2) (i == 2 AND Board[4,4]!= FULL) retur game; else (!IsEmpty(moves[i]) x = Pop (moves[i]); moves[i+] = DoMove(x,Board); game = Push(game,move) else UdoMove(Pop(game), Board); i--; 5. Τα δύο προβλήµατα µπορούν να λυθούν µε αλγόριθµους δυναµικού προγραµµατισµού, παραλλαγές του αλγόριθµου των Floyd-Warshall, και χρόνους 9
εκτέλεσης της τάξης Ο(³). Πιο κάτω δίνονται οι αναδροµικοί ορισµοί των ζητούµενων βέλτιστων λύσεων. Η διατύπωση των αλγορίθµων αφήνεται ως άσκηση στον αναγνώστη. ( Συµβολισµός: Γράφουµε paths[m] για το µέγιστο αριθµό µονοπατιών από την κορυφή i στην κορυφή j µε ενδιάµεσες κορυφές που ανήκουν στο σύνολο {,2,m. Τότε, w( j) paths[ 0] = 0, w( j) = paths[ m] = paths[ m ] + paths[ m ] paths[ m ]) Παρατηρείστε πως για να ορίζεται η έννοια του µέγιστου αριθµού µονοπατιών ανάµεσα στους κόµβους ενός γράφου, ο γράφος πρέπει να µην περιέχει κύκλους. (i Συµβολισµός: Γράφουµε maxpaths[m] για τον αριθµό των βραχύτερων µονοπατιών από την κορυφή i στην κορυφή j µε ενδιάµεσες κορυφές που ανήκουν στο σύνολο {,2,m. Τότε, w( j) maxpaths[ 0] = 0, w( j) = maxpaths[ m ], c( < c( + c( maxpaths[ m ] maxpaths[ m ], maxpaths[ m] = c( > c( + c( maxpaths[ m ] + maxpaths[ m ] maxpaths[ m ], c( = c( + c( όπου τα c[m] είναι τα βάρη βραχύτερων µονοπατιών όπως ορίζονται στον αλγόριθµο των Floy-Warshall. 0