Εισαγωγή στην Ανάλυση Αλγορίθμων (2-3)
3.1 Ασυμπτωτικός συμβολισμός (Ι) Οι ορισμοί που ακολουθούν μας επιτρέπουν να επιχειρηματολογούμε με ακρίβεια για την ασυμπτωτική συμπεριφορά. Οι f(n) και g(n) συμβολίζουν συναρτήσεις οι οποίες απεικονίζουν μη αρνητικούς ακεραίους σε μη αρνητικούς πραγματικούς αριθμούς. Χρησιμοποιούμε συναρτήσεις με πεδίο ορισμού το Ν γιατί τα δεδομένα στην είσοδο έχουν να κάνουν με ακέραια μεγέθη. Επίσης, επειδή μας ενδιαφέρει ο χρόνος εκτέλεσης αλγορίθμων, όλες οι συναρτήσεις που χρησιμοποιούμε για να συμβολίζουν ρυθμούς αύξησης, θα είναι τελικά θετικά ορισμένες (δηλ. έχουν σύνολο τιμών το σύνολο των μη αρνητικών πραγματικών αριθμών R + ). 2
Ασυμπτωτικός συμβολισμός (ΙΙ) Συμβολισμός O Ορισμός: Για δεδομένη συνάρτηση g, συμβολίζουμε με Ο(g(n)) το σύνολο των συναρτήσεων { R N } Ogn ( ( )) = f( n): c, n : n n, f( n) cgn ( ) 0 0 λέμε ότι η g(n) είναι ένα ασυμπτωτικό άνω φράγμα της f(n), ή ότι η f(n) είναι Ο(g(n)), συμβολικά, f(n) = Ο(g(n)) [αν και πιο σωστά, f(n) Ο(g(n))]. Παράδειγμα: 5n 2 + 20n = Ο(n 2 ) Για να το δείξουμε, πρέπει να προσδιορίσουμε μια σταθερά c και έναν n 0 τ.ώ. n n 0, 5n 2 + 20n cn 2 5 + 20/n c ή επειδή 5 + 20/n c αν c 6 με n 20 c = 6 και n 0 = 20. Τα c και n 0 δεν είναι μοναδικά. 3
Ασυμπτωτικός συμβολισμός (ΙΙΙ) Ορισμός: Έστω η συνάρτηση f(n) = O(g(n)). Αν για κάθε συνάρτηση h(n) τέτοια, ώστε f(n) = O(h(n)) ισχύει επίσης ότι g(n) = O(h(n)), τότε λέμε ότι η g(n) είναι ένα αυστηρό ασυμπτωτικό άνω φράγμα της f(n). Παράδειγμα: Έστω f(n) = 8n + 128. Είναι f(n) = Ο(n 2 ). Πράγματι, έχουμε f(n) cn 2 8n + 128 cn 2 cn 2 8n 128 0 Αν c = 1, είναι n 2 8n 128 0 (n + 8)(n 16) 0, που ισχύει για κάθε n 16. Άρα c (=1) και n 0 (=16) τέτοια, ώστε f(n) cn 2 για κάθε n n 0. Μπορούμε τώρα να δείξουμε εύκολα ότι f(n) = Ο(n) (βλ. και Θ.5 παρακάτω). Προφανώς το Ο(n) είναι «αυστηρότερο» άνω φράγμα από το Ο(n 2 ). Με βάση τον παραπάνω ορισμό μπορούμε να δείξουμε ότι η g(n) = n είναι ένα αυστηρό ασυμπτωτικό άνω φράγμα της f(n) = 8n + 128. 4
Ιδιότητες του Ο Θ.1 Αν f 1 (n) = O(g 1 (n)) και f 2 (n) = O(g 2 (n)), τότε f 1 (n) + f 2 (n) = O(max(g 1 (n), g 2 (n)) Θ.2 Αν f 1 (n) = O(g 1 (n)) και f 2 (n) = O(g 2 (n)), τότε f 1 (n) f 2 (n) = O(g 1 (n) g 2 (n)) (Κανόνας Αθροίσματος) (Κανόνας Γινομένου) Θ.3 Αν f 1 (n) = O(g 1 (n)) και g 2 (n) μια συνάρτηση με μη αρνητικές τιμές για κάθε n 0, τότε f 1 (n) g 2 (n) = O(g 1 (n) g 2 (n)) Θ.4 Αν f(n) = O(g(n)) και g(n) = O(h(n)), τότε f(n) = O(h(n)) Θ.5 Αν f(n) = a m n m + a m 1 n m 1 + + a 1 n + a 0 με a m > 0, τότε f(n) = O(n m ) Θ.6 Για κάθε ακέραιο k > 1, log k n = O(n). 5
Συμβάσεις Όταν γράφουμε μια έκφραση Ο παραλείπουμε όλους τους όρους εκτός από τους πλέον σημαντικούς. Π.χ. αντί του Ο(n 2 + nlogn + n + 1) γράφουμε απλά, Ο(n 2 ) Παραλείπουμε σταθερούς συντελεστές. Π.χ. αντί των Ο(3n 2 ), Ο(3) γράφουμε απλά, Ο(n 2 ), Ο(1) Για να είναι μια έκφραση Ο όσο το δυνατόν πιο χρήσιμη, καλόν είναι να βρίσκουμε ένα αυστηρό ασυμπτωτικό άνω φράγμα. Π.χ. αντί να γράψουμε f(n) = n = O(n 3 ) είναι προτιμότερο να γράψουμε f(n) = O(n) 6
Χαρακτηριστικές περιπτώσεις Στον Πιν. 3.1 οι εκφράσεις είναι κατά σειρά αύξοντος μεγέθους 7
Συμβολισμός Ω Συμβολισμός Ω Για δεδομένη συνάρτηση g, συμβολίζουμε με Ω(g(n)) το σύνολο των συναρτήσεων Ω ( gn ( )) = f( n): c R, n N: n n, cgn ( ) f( n) λέμε ότι η g(n) είναι ένα ασυμπτωτικό κάτω φράγμα της f(n), ή ότι η f(n) είναι Ω(g(n)), συμβολικά, f(n) = Ω(g(n)). Παράδειγμα: n = Ω(ln n). Πράγματι, είναι n 1, n > ln n (δηλ. c = 1, n 0 = 1). { } 0 0 8
Συμβολισμός Θ Συμβολισμός Θ Για δεδομένη συνάρτηση g, συμβολίζουμε με Θ(g(n)) το σύνολο των συναρτήσεων με άλλα λόγια, η f (n) είναι «ίση» με την g(n), με προσέγγιση σταθερού συντελεστή και λέμε ότι η g(n) είναι ένα ασυμπτωτικό φράγμα της f(n). Πρόταση: { R N } Θ ( gn ( )) = f( n): c, c, n : n n, cgn ( ) f( n) cgn ( ) 1 2 0 0 1 2 Για δύο συναρτήσεις f(n) και g(n) ισχύει η ισοδυναμία f(n) = Θ(g(n)) f(n) = O(g(n)) και f(n) = Ω(g(n)) 9
Συμβολισμός Θ Παράδειγμα: 5n 2 + 20n = Θ(n 2 ). Για να το δείξουμε, πρέπει να προσδιορίσουμε σταθερές c 1, c 2 και n 0 τ.ώ. n n 0, c 1 n 2 5n 2 + 20n c 2 n 2 ή c 1 5 + 20/n c 2 ή επειδή c 1 5 + 20/n αν c 1 5 με n > 1 και 5 + 20/n c 2 αν c 2 6 με n 20 έχουμε c 1 = 5, c 2 = 6 και n 0 = 20. Τα c 1, c 2 και n 0 δεν είναι μοναδικά. Παρατήρηση: f(n) = Θ(g(n)) f(n) = O(g(n)) αφού, Θ(g(n)) O(g(n)). Πρόταση: Αν f(n) = a m n m + a m 1 n m 1 + + a 1 n + a 0 με a m > 0, τότε f(n) = Θ(n m ). Ιδιότητα: n n Θ ( f( k) ) =Θ f( k). k= 1 k= 1 10
Ασυμπτωτική συμπεριφορά πολυωνύμων 11
Κανόνας του ορίου Ένα ισχυρό και χρήσιμο εργαλείο για να αποδείξει κανείς ότι κάποιες συναρτήσεις είναι της τάξης κάποιων άλλων, όπως επίσης και για να αποδείξει το αντίθετο, είναι ο κανόνας του ορίου, ο οποίος μας λέει ότι δοθέντων δύο συναρτήσεων f και g: N R+, έχουμε 12
(ή αλλιώς ) Κανόνας του ορίου Αν τα όρια πηλίκου οδηγούν σε απροσδιόριστες μορφές 0/0 ή / εφαρμόζεται ο κανόνας του de l Hopital 13
Ένα παράδειγμα Εστω οι συναρτήσεις f(n) = 2 n και g(n) = 3 n υπολογίστε το ασυμπτωτικό φράγμα για την f(n). Η απόδειξη θα γίνει με τον κανόνα του ορίου. Με τον κανόνα του de L Hopital έχουμε Οπότε για 2 lim 3 n n n 3 Χρησιμοποιώντας την ιδιότητα n Το αποτέλεσμα της διαίρεσης συγκλίνει στο 0 άρα Όμως και με τον κανόνα του de L Hopital δε μπορούμε να προχωρήσουμε καθώς και ο αριθμητής και ο παρονομαστής συνεχίζουν να αποκλίνουν. 2 = lim n lima n + n + 14 14
Ασυμπτωτική Εκτίμηση Χρόνος εκτέλεσης αλγόριθμου Α: Αύξουσα συνάρτηση του Τ(n) που εκφράζει σε πόσο χρόνο ολοκληρώνεται ο Α όταν εφαρμόζεται σε στιγμ. μεγέθους n. Ενδιαφέρει η τάξη μεγέθους T(n) και όχι ακριβής εκτίμηση Τ(n). Ακριβής εκτίμηση είναι συχνά δύσκολη και εξαρτάται από υπολογιστικό περιβάλλον, υλοποίηση,... Τάξη μεγέθους είναι εγγενής ιδιότητα του αλγόριθμου. Δυαδική αναζήτηση έχει λογαριθμικό χρόνο. Γραμμική αναζήτηση έχει γραμμικό χρόνο. Ασυμπτωτική εκτίμηση αγνοεί σταθερές και εστιάζει σε τάξη μεγέθους χρόνου εκτέλεσης. 15
Ασυμπτωτικός Συμβολισμός Ο, Ω, Θ... εκφράζει τα αποτελέσματα ασυμπτωτικής εκτίμησης. Θ( ) δηλώνει την ακριβή εκτίμηση τάξης μεγέθους. { R N } Θ ( gn ( )) = f( n): c, c, n : n n, cgn ( ) f( n) cgn ( ) 1 2 0 0 1 2 Θ(g(n)) σύνολο συναρτήσεων ίδιας τάξης μεγέθους με g(n). O( ) δηλώνει άνω φράγμα στην τάξη μεγέθους. Ogn ( ( )) = f( n): c R, n N: n n, f( n) cgn ( ) { } 0 0 Ο(g(n)) σύνολο συναρτήσεων με τάξη μεγέθους που δεν υπερβαίνει τάξη μεγέθους g(n). Ω( ) δηλώνει κάτω φράγμα στην τάξη μεγέθους. { R N } Ω ( gn ( )) = f( n): c, n : n n, cgn ( ) f( n) 0 0 Ω(g(n)) σύνολο συναρτήσεων με τάξη μεγέθους που δεν υπολείπεται τάξης μεγέθους g(n). 16
Για κάθε ακέραιο m > 1, η λογαριθμική συνάρτηση g(n) = log m (n) έχει τον ίδιο ρυθμό αύξηση με την log 2 (n) (ή lgn) γιατί : Από τις ιδιότητες των λογαρίθμων ισχύει: log m n= lοg 2 n/ log 2 m => log m n = (1/log 2 m) lοg 2 n για κάθε n>0. Συνεπώς καθώς ο όρος 1/log 2 m είναι σταθερά και ανεξάρτητη της τιμής του n, μπορούμε να παραλείπουμε τη βάση του λογαρίθμου όταν χρησιμοποιούμε το συμβολισμό Ο (και το συμβολισμό Θ). Αρα : log m n = Θ(logn) Οι εκθετικές συναρτήσεις αυξάνουν ταχύτερα από τις συναρτήσεις δύναμης: συνεπώς n k είναι O(b n ), για όλα τα b > 1, n > 1, and k 0. Οι περιορισμοί για τα b, n, και k εγγυώνται ότι και οι 2 συναρτήσεις είναι αύξουσες. (Η απόδειξη της πρότασης μπορεί να γίνει με επαγωγή ή με τον κανόνα του ορίου του de L Hopital). Οι λογαριθμικές συναρτήσεις έχουν μικρότερο ρυθμό αύξησης από τις συναρτήσεις δύναμης : log b n = O(n k ) για κάθε b > 1, k > 0. (είναι το αντίστροφο της προηγούμενης πρότασης). Αρα logn = O(n) και nlogn = O(n 2 ). 17
Ασυμπτωτικός Συμβολισμός 18
Ασυμπτωτικός Συμβολισμός 19
Practical Complexity 20
Πρακτικά αποδοτικοί Αλγόριθμοι 21
3.2 Ασυμπτωτική ανάλυση αλγορίθμων (I) Στα προηγούμενες ενότητες παρουσιάσαμε ένα λεπτομερές μοντέλο του υπολογιστή το οποίο περιλαμβάνει αρκετές παραμέτρους χρονομέτρησης και διαπιστώσαμε ότι πρόκειται για λεπτομέρειες που μπορούν να απλουστευθούν. Έτσι στη συνέχεια απλοποιήσαμε το μοντέλο μετρώντας τον χρόνο σε κύκλους ρολογιού και υποθέτοντας ότι κάθε μία από τις παραμέτρους είναι ίση με έναν κύκλο. Σε μεγάλο βαθμό εξακολουθεί το απλοποιημένο μοντέλο να περιλαμβάνει λεπτομέρειες τις οποίες μπορούμε να αγνοήσουμε. Στην ενότητα αυτή παρουσιάζουμε την έννοια των ασυμπτωτικών φραγμάτων κυρίως του Ο (μεγάλο όμικρον). Εξετάζοντας αυτά τα φράγματα μπορούμε να πούμε ότι οι κανόνες υπολογισμού και χειρισμού των εκφράσεων Ο απλοποιούν τα μέγιστα την ανάλυση του χρόνου εκτέλεσης ενός προγράμματος, όταν αυτό που μας ενδιαφέρει είναι η ασυμπτωτική του συμπεριφορά. Προκειμένου να αναλύσουμε έναν αλγόριθμο θα θεωρήσουμε το μοντέλο της μηχανής άμεσης ή τυχαίας προσπέλασης (Random Access Machine - RAM) με έναν επεξεργαστή όπου οι εντολές εκτελούνται ακολουθιακά (η μία κατόπιν της άλλης), δηλ. δεν υπάρχουν πράξεις που να εκτελούνται ταυτόχρονα. 22
Ασυμπτωτική ανάλυση αλγορίθμων (IΙ) Το μοντέλο RAM περιλαμβάνει εντολές οι οποίες υπάρχουν σε όλους τους πραγματικούς υπολογιστές: - αριθμητικές πράξεις (πρόσθεση, αφαίρεση, πολλαπλασιασμός, διαίρεση, υπόλοιπο, ακέραιο μέρος) - εντολές μεταφοράς δεδομένων (ανάκληση, αποθήκευση, αντιγραφή) - εντολές ελέγχου (διακλάδωση υπό συνθήκη και άνευ συνθήκης, κλήση υποπρογράμματος και επιστροφή) Όλες αυτές οι εντολές απαιτούν κάποιον σταθερό χρόνο για την εκτέλεσή τους. Ο χρόνος εκτέλεσης εξαρτάται από τα δεδομένα εισόδου. Γενικά, ο απαιτούμενος χρόνος αυξάνεται καθώς αυξάνεται το μέγεθος της εισόδου και γι αυτό συνηθίζεται να εκφράζουμε τον χρόνο εκτέλεσης ενός προγράμματος ως συνάρτηση του μεγέθους της εισόδου. Είναι αναγκαίο να διευκρινίσουμε τους όρους χρόνος εκτέλεσης και μέγεθος εισόδου. Το ποιο είναι το καταλληλότερο μέτρο για το μέγεθος εισόδου εξαρτάται από το πρόβλημα. Σε κάθε πρόβλημα που εξετάζουμε πρέπει να δηλώνουμε ρητά ποιο μέτρο χρησιμοποιούμε για το μέγεθος της εισόδου. 23
Ασυμπτωτική ανάλυση αλγορίθμων (IΙΙ) Για πολλά προβλήματα, όπως π.χ. η ταξινόμηση, το μέτρο θα είναι το πλήθος των στοιχείων της εισόδου. Για άλλα προβλήματα, όπως π.χ. ο πολ/σιασμός δύο ακεραίων, το μέτρο θα είναι το συνολικό πλήθος bit που απαιτούνται για την αναπαράσταση της εισόδου στο δυαδικό σύστημα. Ενίοτε είναι ενδεδειγμένο να περιγράφουμε το μέγεθος της εισόδου με δύο αριθμούς, όπως για παράδειγμα, όταν η είσοδος είναι ένας γράφος (πλήθος κόμβων, πλήθος ακμών). Ο χρόνος εκτέλεσης ενός αλγορίθμου για δεδομένη είσοδο είναι το πλήθος των στοιχειωδών πράξεων ή βημάτων που εκτελούνται, όπου το βήμα πρέπει να ορίζεται έτσι ώστε να είναι κατά το δυνατόν πιο ανεξάρτητο από τον τύπο της μηχανής. Θα κάνουμε την παραδοχή (συμβατή με το μοντέλο RAM) ότι ο χρόνος εκτέλεσης για κάθε γραμμή του προγράμματος ή του ψευδοκώδικά μας είναι σταθερός και αν και η μια γραμμή μπορεί να απαιτεί διαφορετικό χρόνο από την άλλη, θα υποθέσουμε ότι κάθε εκτέλεση της i-οστής γραμμής απαιτεί χρόνο c i, όπου c i = σταθ. (= Ο(1), με τον ασυμπτωτικό συμβολισμό). Θεωρούμε, για παράδειγμα, την ανάλυση του χρόνου εκτέλεσης του Προγ. 2.1, που είναι ο αλγόριθμος υπολογισμού της τιμής ενός πολυωνύμου μe τον κανόνα του Horner: 24
Ασυμπτωτική ανάλυση αλγορίθμων (IV) 1. int Horner (int a[ ], int n, int x) 2. { 3. int result = a[n]; 4. for ( int i = n -1; i >= 0; - -i ) 5. result = result * x + a[i]; 6. return result; 7. } 25
Ασυμπτωτική ανάλυση αλγορίθμων (V) Στον Πιν. 3.1 παρουσιάζουμε τον χρόνο εκτέλεσης με τρεις τρόπους: μια λεπτομερή ανάλυση, μια απλουστευμένη ανάλυση και μια ασυμπτωτική ανάλυση. Και οι τρεις τρόποι είναι σε συμφωνία: οι εντολές 3, 4a και 6 εκτελούνται σε μια σταθερή ποσότητα χρόνου και οι εντολές 4b, 4c και 5 εκτελούνται σε μια ποσότητα χρόνου που είναι ανάλογη του n συν μία σταθερά (γραμμική). Το αποτέλεσμα της ασυμπτωτικής ανάλυσης δεν εξαρτάται από τις τιμές των διαφόρων σταθερών και ως εκ τούτου το ασυμπτωτικό φράγμα μας λέει κάτι το θεμελιώδες για τον χρόνο εκτέλεσης του αλγορίθμου, το οποίο δεν εξαρτάται από τα χαρακτηριστικά του υπολογιστή (και του μεταγλωττιστή) που χρησιμοποιείται για την εκτέλεση του προγράμματος. Βέβαια, ενώ η ασυμπτωτική ανάλυση μπορεί να είναι σημαντικά απλούστερη, το μόνο που μας δίνει είναι ένα άνω φράγμα του χρόνου εκτέλεσης του αλγορίθμου. Ειδικότερα, δεν μαθαίνουμε τον πραγματικό χρόνο εκτέλεσης ενός προγράμματος. 26
3.3 Κανόνες για την ανάλυση Ο του χρόνου εκτέλεσης (Ι) Κανόνας 1 (Ακολουθιακή σύνθεση) Ο χρόνος εκτέλεσης χειρότερης περίπτωσης μιας ακολουθίας εντολών E 1 ; E 2 ; E k ; είναι Ο(max(T 1 (n),, T k (n))), όπου ο χρόνος εκτέλεσης της E i, της i-οστής εντολής της ακολουθίας, είναι O(T i (n)). O κανόνας αυτός απορρέει από το Θ.1. Ο συνολικός χρόνος εκτέλεσης μιας ακολουθίας εντολών είναι το άθροισμα των χρόνων εκτέλεσης των επιμέρους εντολών. Με βάση το Θ.1, όταν υπολογίζουμε το άθροισμα μιας σειράς από συναρτήσεις, είναι η μεγαλύτερη (max) απ αυτές που καθορίζει το φράγμα. 27
Κανόνες για την ανάλυση Ο του χρόνου εκτέλεσης (ΙΙ) Κανόνας 2 (Επανάληψη) Ο χρόνος εκτέλεσης χειρότερης περίπτωσης ενός βρόχου for, for (E 1 ; E 2 ; E 3 ;) E 4 ; είναι Ο(max(T 1 (n), T 2 (n) (Ι(n) + 1), T 3 (n) Ι(n), T 4 (n) Ι(n))), όπου ο χρόνος εκτέλεσης της εντολής E i είναι O(T i (n)) για i = 1, 2, 3 και 4, και Ι(n) είναι το πλήθος των επαναλήψεων που εκτελούνται στη χειρότερη περίπτωση. Ο κανόνας αυτός είναι απόρροια του Θ.3. Έστω π.χ. ο απλός βρόχος for (int i = 0; i < n; ++i;) E 4 ; Εδώ E 1 είναι int i = 0, οπότε ο χρόνος εκτέλεσής της είναι μια σταθερά (T 1 (n) = 1). E 2 είναι i < n, οπότε ο χρόνος εκτέλεσής της είναι μια σταθερά (T 2 (n) = 1) και E 3 είναι ++i οπότε ο χρόνος εκτέλεσής της είναι μια σταθερά (T 3 (n) = 1). Επίσης, το πλήθος των επαναλήψεων είναι I(n) = n. 28
Κανόνες για την ανάλυση Ο του χρόνου εκτέλεσης (ΙΙΙ) Σύμφωνα με τον Κανόνα 2 ο χρόνος εκτέλεσης θα είναι Ο(max(1, 1 (n +1), 1 n, T 4 (n) n)), που απλοποιείται σε Ο(max(n, T 4 (n) n)). Επιπλέον, αν το σώμα του βρόχου κάνει οτιδήποτε, ο χρόνος εκτέλεσής του πρέπει να είναι T 4 (n)=ω(1). Άρα, το σώμα του βρόχου θα κυριαρχεί στον υπολογισμό του max και ο χρόνος εκτέλεσης του βρόχου είναι απλά Ο(T 4 (n) n)). Αν δεν γνωρίζουμε τον ακριβή αριθμό I(n) των επαναλήψεων που εκτελούνται, μπορούμε πάλι να χρησιμοποιήσουμε τον Κανόνα 2, με την προϋπόθεση ότι έχουμε ένα άνω φράγμα, I(n) = Ο(f(n)), του πλήθους των επαναλήψεων που εκτελούνται. Στην περίπτωση αυτή, ο χρόνος εκτέλεσης είναι Ο(max(T 1 (n), T 2 (n) (f(n) + 1), T 3 (n) f(n), T 4 (n) f(n))) 29
Κανόνες για την ανάλυση Ο του χρόνου εκτέλεσης (ΙV) Κανόνας 3 (Εκτέλεση υπό συνθήκη) Ο χρόνος εκτέλεσης χειρότερης περίπτωσης μιας εντολής if-then-else, if (E 1 ;) E 2 ; else E 3 ; είναι Ο(max(T 1 (n), T 2 (n), T 3 (n))), με τον χρόνο εκτέλεσης της εντολής E i να είναι Ο(T i (n)), για i = 1, 2, 3. Ο κανόνας αυτός απορρέει από την παρατήρηση ότι, ο συνολικός χρόνος μιας εντολής if-then-else δεν υπερβαίνει το άθροισμα του χρόνου εκτέλεσης του ελέγχου συνθήκης, E 1, και του μεγαλύτερου από τους χρόνους εκτέλεσης του τμήματος then, E 2, και του τμήματος else, E 3. 30
Παράδειγμα 1 (Ι) Πρόβλημα: Υπολογισμός των όρων της ακολουθίας αθροισμάτων (σειρά) S 0, S 1,, S n-1, όπου j Sj = ai i= 0 Ο αλγόριθμος για τον υπολογισμό των όρων της σειράς δίνεται στο Προγ. 3.1 και στον Πιν. 3.2 συνοψίζουμε τον υπολογισμό του χρόνου εκτέλεσης 1. void PrefixSums (int a[], int n) 2. { 3. for ( int j = n - 1; j >= 0; - -j ) 4. { 5. int sum = 0; 6. for (int i = 0; i <= j; ++i) 7. sum += a[i]; 8. a[j] = sum; 9. } 10.} 31
1. void PrefixSums (int a[], int n) 2. { 3. for ( int j = n - 1; j >= 0; - -j ) 4. { 5. int sum = 0; 6. for (int i = 0; i <= j; ++i) 7. sum += a[i]; 8. a[j] = sum; 9. } 10. } 32
Παράδειγμα 1 (ΙΙ) Συνήθως ο ευκολότερος τρόπος για να αναλύσουμε τον χρόνο εκτέλεσης ενός προγράμματος που περιλαμβάνει ένθετους βρόχους είναι να αρχίσουμε με το σώμα του πλέον εσωτερικού βρόχου. Στο Προγ. 3.1 τον πλέον εσωτερικό βρόχο συνιστούν οι γραμμές 6 και 7. Συνολικά το κόστος είναι σταθερό: συμπεριλαμβάνει το σώμα του βρόχου (γρ. 7), τον έλεγχο συνθήκης (γρ. 6b) και την προσαύξηση του δείκτη βρόχου (γρ. 6c). Για μια δεδομένη τιμή του j, ο (πλέον) εσωτερικός βρόχος εκτελείται συνολικά j + 1 φορές. Και αφού ο εξωτερικός βρόχος εκτελείται για j = n 1, n 2,, 0, στη χειρότερη περίπτωση, ο εσωτερικός βρόχος εκτελείται n φορές. Επομένως η συνεισφορά του εσωτερικού βρόχου στον χρόνο εκτέλεσης μιας επανάληψης του εξωτερικού βρόχου είναι Ο(n). To υπόλοιπο του εξωτερικού βρόχου (γρ. 5 και 8) απαιτεί σταθερό χρόνο σε κάθε επανάληψη. Αυτός ο σταθερός χρόνος υπερκαλύπτεται από τον Ο(n) του εσωτερικού βρόχου. Ο εξωτερικός βρόχος κάνει ακριβώς n επαναλήψεις. Επομένως ο συνολικός χρόνος εκτέλεσης του προγράμματος είναι Ο(n 2 ). Είναι αυτό ένα αυστηρό Ο φράγμα; 33
Παράδειγμα 1 (ΙIΙ) Είναι λογικό να μην είναι, εξαιτίας της υπόθεσης που κάναμε στην ανάλυση για τη χειρότερη περίπτωση σε σχέση με τον αριθμό των εκτελέσεων του εσωτερικού βρόχου. Ο εσωτερικός βρόχος εκτελείται j + 1 φορές για j = n 1, n 2,, 0. Κάναμε όμως τον υπολογισμό υποθέτοντας ότι ο εσωτερικός βρόχος εκτελείται O(n) φορές σε κάθε επανάληψη του εξωτερικού βρόχου. Για να προσδιορίσουμε όμως αν το αποτέλεσμά μας είναι ένα αυστηρό φράγμα, θα πρέπει να προσδιορίσουμε ακριβέστερα τον πραγματικό χρόνο εκτέλεσης του προγράμματος. Υπάρχει ακριβέστερος τρόπος υπολογισμού που μπορούμε να κάνουμε. Αν παρατηρήσουμε ότι ο χρόνος που καταναλώνεται στον εσωτερικό βρόχο κυριαρχεί (είναι το βαρόμετρο) στον συνολικό χρόνο εκτέλεσης και ότι το κόστος για μια επανάληψη του εσωτερικού βρόχου είναι σταθερό, τότε το μόνο που χρειάζεται να κάνουμε είναι να προσδιορίσουμε ακριβώς το πλήθος των εκτελέσεων του εσωτερικού βρόχου. Αυτό δίνεται από το άθροισμα n 1 n nn ( + 1) 2 ( j+ 1) = j = = Θ( n ) 2 j= 0 j= 1 Επομένως το αποτέλεσμά μας, T(n) = O(n 2 ), είναι ένα αυστηρό Ο φράγμα. 34
Παράδειγμα 2 Θα συγκρίνουμε τους χρόνους εκτέλεσης δύο διαφορετικών προγραμμάτων που υπολογίζουν τους όρους της ακολουθίας Fibonacci (αριθμοί Fibonacci) οι οποίοι δίνονται από τη σχέση Το Προγ. 3.2 είναι μια άμεση υλοποίηση του ορισμού του F n ως αθροίσματος των δύο προηγούμενων όρων: 1. int Fibonacci (int n) 2. { 3. int previous = -1; 4. int result = 1; 5. for ( int i = 0; i <= n; ++i ) 6. { 7. int sum = result + previous; 8. previous = result; 9. result = sum; 10. } 11. return result; 12.} 35
Ο χρόνος εκτέλεσης αυτού του αλγορίθμου είναι Ο(n) όπως αναλυτικά παρουσιάζεται στον Πιν. 3.3. Η ακολουθία Fibonacci είναι αναδρομική αλλά ο αλγόριθμος του Προγ. 3.2 είναι επαναληπτικός. Ένας αναδρομικός αλγόριθμος δίνεται στο Προγ. 3.3 και ο χρόνος εκτέλεσής του παρουσιάζεται στον Πιν. 3.4: 36
1. int Fibonacci (int n) 2. { 3. if (n = = 0 n = = 1) 4. return n; 5. else 6. return Fibonacci(n 1) * Fibonacci (n 2); 7. } 37
Από τον Πιν. 3.4 βρίσκουμε ότι ο χρόνος εκτέλεσης του αναδρομικού αλγορίθμου για τους αριθμούς Fibonacci δίνεται από την αναδρομική σχέση Επειδή μας ενδιαφέρει η ασυμπτωτική συμπεριφορά (το ασυμπτωτικό φράγμα) του αποτελέσματος, απλά αγνοούμε προς στιγμήν τα Ο και λύνουμε την αναδρομική σχέση Πρόκειται για τη μη ομογενή γραμμική αναδρομική σχέση 2 ου βαθμού α n = α n-1 + α n-2 + 1 (1) με a 0 = 1 και a 1 = 1 (αρχικές συνθήκες) Για να τη λύσουμε εφαρμόζουμε τη γνωστή, από το μάθημα των Διακριτών Μαθηματικών, διαδικασία επίλυσης γραμμικών αναδρομικών εξισώσεων με σταθερούς συντελεστές. 38
Η αντίστοιχη ομογενής εξίσωση είναι με χαρακτηριστική εξίσωση x 2 x 1 = 0 a a a n n 1 n 2 = 0 οι ρίζες της χαρακτηριστικής εξίσωσης βρίσκουμε ότι είναι ( + ) ( ) ρ = 1 5 2 και ρ = 1 5 2= 1 ρ 1 2 1 η γενική λύση της ομογενούς αναδρομικής εξίσωσης είναι της μορφής a = c ρ + c ρ ( H) n n n 1 1 2 2 Επειδή το μη ομογενές τμήμα είναι 1, αναζητούμε μερική λύση της μη ομογενούς της μορφής ( P ) an = Με αντικατάσταση στην (1) βρίσκουμε c = -1, οπότε η γενική λύση της (1) είναι a a a c c ( H) ( P) n n n = n + n = 1 ρ1 + 2 ρ2 1 Οι σταθερές βρίσκονται με αντικατάσταση στις αρχικές συνθήκες: c 1 + c 2 = 2 c 1 ρ 1 + c 2 ρ 2 = 2 39 c
Λύνοντας το σύστημα βρίσκουμε c ( 1+ 5) c ( 1 5) = 5 και = 5 1 2 Τελικά η λύση της αναδρομικής σχέσης είναι ( ) n ( ) a = 2 5 ρ 2 5 ρ 1 n + 1 n+ 1 1 2 ( )( n+ 1 n+ 1) 1 2 Tn ( ) = 2 5 ρ ρ 1 Είναι ρ 1 = φ 1.622 και ρ 2 = 1/φ 0.62 οπότε T(n) = Θ(φ n ) 40