Προβλήματα, αλγόριθμοι, ψευδοκώδικας October 11, 2011 Στο μάθημα Αλγοριθμική και Δομές Δεδομένων θα ασχοληθούμε με ένα μέρος της διαδικασίας επίλυσης υπολογιστικών προβλημάτων. Συγκεκριμένα θα δούμε τι εννοούμε όταν αναφερόμαστε στην έννοια του προβλήματος και πώς σχεδιάζουμε αλγορίθμους δηλαδή διαδικασίες που αφενός λύνουν τα προβλήματα αυτά και αφετέρου μπορούν εύκολα να εκτελεστούν από έναν υπολογιστή αν μεταφραστούν κατάλληλα. Κατ' αρχάς, τα προβλήματα με τα οποία θα ασχοληθούμε είναι υπολογιστικά δηλαδή εμπεριέχουν μετρήσιμες αριθμητικές ποσότητες. Με τον όρο πρόβλημα (problem) αναφερόμαστε σε ένα ερώτημα για το οποίο ψάχνουμε απάντηση του οποίου κάποιες παράμετροι είναι ανοιχτές, δηλαδή δεν έχουν συγκεκριμένες τιμές. Κάθε πρόβλημα περιγράφεται ως ένα ζευγάρι εισόδου/εξόδου όπου είσοδος είναι τα δεδομένα του προβλήματος και η έξοδος είναι η περιγραφή των περιορισμών που θέλουμε να πληρεί η όποια λύση. Π.χ. το πρόβλημα ταξινόμησης ενός συνόλου αριθμών σε αύξουσα σειρά μπορεί να τεθεί ως εξής: Είσοδος: Ένα σύνολο n αριθμών Έξοδος: Οι ίδιοι αριθμοί διατεταγμένοι έτσι ώστε ο κάθε ένας να είναι μικρότερος ή ίσος από τον επόμενό του. Παρατηρήστε ότι στη διατύπωση του προβλήματος το πλήθος των αριθμών και το ποιοι είναι αυτοί δεν συγκεκριμενοποιείται. Με τον όρο στιγμιότυπο (instance) ενός προβλήματος αναφερόμαστε σε ένα πρόβλημα με συγκεκριμένες τιμές στην διατύπωσή του. Ένα άλλο πρόβλημα είναι και το πρόβλημα του πλανώδιου πωλητή: Είσοδος: Ένα σύνολο πόλεων και οι μεταξύ τους αποστάσεις. Έξοδος: Μια διαδρομή από την έδρα του πωλητή που περνάει από όλες τις πόλεις, καταλήγει πίσω στην έδρα του και είναι συντομότερη από οποιαδήποτε άλλη. 1
Το πρόβλημα του πλανώδιου πωλητή είναι εξαιρετικά δύσκολο. Άσχετα από υπολογιστές, είναι προφανές ότι για να προσπαθήσει κανείς να βρει μία ικανοποιητική λύση στο πρόβλημα πρέπει να ακολουθήσει μια μέθοδο ειδικά σχεδιασμένη για αυτό το σκοπό. Όμως ακόμα και σε πιο εύκολες περιπτώσεις όπως αυτή της ταξινόμησης κάποιων αριθμών δεν είναι πάντα σίγουρο ότι ο τρόπος που θα έλυνε ένας άνθρωπος το πρόβλημα είναι εφικτό να μεταφερθεί σε έναν υπολογιστή. Π.χ. ένας άνθρωπος θα μπορούσε να ταξινομήσει ένα σύνολο αριθμών `κοιτώντας' τους, βάζοντας πρώτο τον μικρότερο, μετά κοιτώντας τους υπόλοιπους και βάζοντας δεύτερο τον μικρότερο από εκείνους κοκ. Δυστυχώς ένας υπολογιστής δεν μπορεί απλώς να `κοιτάξει' ένα σύνολο αριθμών όπως άλλωστε δεν μπορεί ούτε ένας άνθρωπος αν πρόκειται για ένα δισεκατομμύριο αριθμούς. Το ζητούμενο λοιπόν είναι να σχεδιάσουμε διαδικασίες οι οποίες είναι σαφείς και εφικτές για έναν υπολογιστή. Τελικά οι υπολογιστές μπορούν απλώς να κάνουν πράξεις (αριθμητικές και λογικές), να διαβάζουν και να γράφουν σε κάποιο είδος μνήμης και πιθανώς να δέχονται την είσοδο ενός προβλήματος από μία μονάδα εισόδου (πληκτρολόγιο, διάτρητες κάρτες) και να δίνουν τη λύση σε κάποια μονάδα εξόδου (οθόνη, εκτυπωτή). Συνθέτοντας τέτοιου είδους βήματα φτιάχνουμε αλγόριθμους. Παρόλο που η έννοια του αλγορίθμου δεν έχει ακόμα οριστεί αυστηρά μπορούμε να πούμε ότι ένας αλγόριθμος πρέπει να έχει τα παρακάτω χαρακτηριστικά: Αποτελείται από ένα πεπερασμένο σύνολο υπολογιστικών βημάτων, τα βήματα αυτά είναι σαφή, τερματίζει μετά από πεπερασμένο αριθμό βημάτων, δίνει τη σωστή λύση στο πρόβλημα. Ένας αλγόριθμος μπορεί να περιγραφεί με διάφορους τρόπους, όπως φυσική γλώσσα, ψευδοκώδικα, διαγράμματα ροής, γλώσσα προγραμματισμού κτλ. Η φυσική γλώσσα προγραμματισμού κ.α. Η φυσική γλώσσα είναι μεν εύκολη λύση για τον άνθρωπο που σχεδιάζει τον αλγόριθμο αλλά είναι πολλές φορές αμφίσημη και απέχει από τη λογική των γλωσσών προγραμματισμού που τελικά θα χρησιμοποιηθούν. Οι γλώσσες προγραμματισμού στο άλλο άκρο είναι πολύ συγκεκριμένες, εμπλέκουν συνήθως τεχνικές λεπτομέρειες καθιστώντας δυσκολότερη την κατανόηση του σκεπτικού της επίλυσης του προβλήματος. Συνήθως χρησιμοποιείται ψευδοκώδικας (pseudocode) δηλαδή ένας κώδικας που στηρίζεται σε απλές λειτουργίες κοινές σε όλους τους υπολογιστές και όλες τις γλώσσες προγραμματισμού χωρίς όμως να υπεισέρχεται σε τεχνικές λεπτομέρειες. 2
Δεν υπάρχει κανένας τυπικός ορισμός της έννοιας του ψευδοκώδικα όμως είναι κοινός τόπος ότι οποιαδήποτε γλώσσα χρησιμοποιεί τις εντολές που ακολουθούν είναι αρκετά σαφής για να είναι μονοσήμαντη, χρησιμοποιεί απλές λειτουργίες κοινές σε όλους τους υπολογιστές και προφανής κατά τη μεταφορά της σε κάποια γλώσσα προγραμματισμού. Παρόλο που ο ψευδοκώδικας είναι `λογοτεχνία' με την έννοια ότι δεν μπορεί ως έχει να εκτελεστεί από έναν υπολογιστή, βοηθάει πολύ να θεωρήσουμε έναν εικονικό υπολογιστή ο οποίος `εκτελεί' τον ψευδοκώδικα. Ο εικονικός αυτός υπολογιστής θα ακολουθεί το μοντέλο που λίγο-πολύ ταιριάζει σε κάθε σύγχρονο υπολογιστή: Θα έχει μια κεντρική μονάδα επεξεργασίας η οποία θα μπορεί να εκτελεί αριθμητικές (πρόσθεση, διαίρεση κτλ.) και λογικές (π.χ. σύγκριση) πράξεις. Επίσης θα έχει και μία μνήμη η οποία θα αποτελείται απο αριθμημένες θέσεις στις οποίες οποίες η κεντρική μονάδα επεξεργασίας μπορεί να γράφει αριθμητικές τιμές όπως επίσης και να διαβάζει από αυτές. Επίσης οι εικονικός μας υπολογιστής θα μπορεί να εισάγει δεδομένα από τον έξω κόσμο, όπως επίσης και εξάγει τα αποτελέσματα των υπολογισμών του. Algorithm 1 Ανάθεση τιμής σε μεταβλητή x 0 Ο παραπάνω ψευδοκώδικας δηλώνει ότι στη μεταβλητή x θα ανατεθεί η τιμή 0. Οι μεταβλητές στον ψευδοκώδικα όπως και στον προγραμματισμό υπολογιστών δεν έχουν την έννοια της ελεύθερης ποσότητας όπως στις μεταβλητές των μαθηματικών συναρτήσεων. Οι μεταβλητές εδώ λειτουργούν ως `αποθηκευτικός χώρος' στον οποίο ένας αλγόριθμος μπορεί να βάλει μια αριθμητική τιμή. Η τιμή τους αλλάζει μόνο αν κάτι τέτοιο δηλώνεται ρητά σε μία εντολή της μορφής x όπου δηλαδή το όνομά της εμφανίζεται αριστερά από το σύμβολο. Σε έναν εικονικό υπολογιστή η ανάθεση τιμής σε μια μεταβλητή είναι ισοδύναμη με την αποθήκευση της τιμής αυτής σε μία θέση μνήμης στην οποία έχει αντιστοιχιστεί μόνιμα το όνομα της μεταβλητής. Προκειμένου να μπορέσουμε να χειριστούμε καταστάσεις στις οποίες ο `χρήστης' θα δίνει από κάποια μονάδα εισόδου την τιμή που θα αποθηκευτεί σε κάποια μεταβλητή θα χρησιμοποιούμε την εντολή ΔΙΑΒΑΣΕ όπως παρακάτω. Με Algorithm 2 Εισαγωγή τιμής ΔΙΑΒΑΣΕ x αυτήν την εντολή στον ψευδοκώδικα εννούμε ότι ζητείται από τον χειριστή 3
του προγράμματος να δώσει μια τιμή η οποία και θα ανατεθεί στη μεταβλητή x. Αντίστοιχα, θα χρησιμοποιούμε την εντολή ΤΥΠΩΣΕ όταν θέλουμε να δώσουμε ένα αποτέλεσμα ως έξοδο. Algorithm 3 Έξοδος ΤΥΠΩΣΕ x Algorithm 4 Υπολογισμός εκφράσεων με μεταβλητές x z + (32 y)/7 Ο παραπάνω ψευδοκώδικας πολλαπλασιάζει την τιμή της μεταβλητής y με το 32, διαιρεί το αποτέλεσμα με τον αριθμό 7 και σε αυτό προσθέτει τημ τιμή της μεταβλητής z. Στο συγκεκριμένο παράδειγμα το τελικό αποτέλεσμα ανατίθεται στη μεταβλητή x αν και αυτό δεν είναι απαραίτητο. Ο υπολογισμός της παράστασης δεξιά από το θα εκτελεστεί άσχετα από το τι θα συμβεί με το αποτέλεσμά του. Σε εκφράσεις τέτοιου τύπου μπορούν να χρησιμοποιηθούν οι γνωστοί τελεστές των μαθηματικών (+,, κτλ.) αλλά και άλλοι όπως ο τελεστής υπολοίπου ακέραιας διαίρεσης % αρκεί να μην υπάρχει ασάφεια και η πράξη να είναι εκτελέσιμη σε υπολογιστή. Σε έναν εικονικό υπολογιστή η όλη διαδικασία θα ήταν ισοδύναμη με την ανάκληση τιμών από τις αντίστοιχες θέσεις μνήμης στην κεντρική μονάδα επεξεργασίας και την πραγματοποίηση των αντίστοιχων πράξεων. Algorithm 5 Εκτέλεση υπό συνθήκη (πρώτη μορφή) 1: if x > y then 2: x x 1 3: πιθανώς περισσότερος ψευδοκώδικας 4: end if 5:... Κατά την εκτέλεση του παραπάνω ψευδοκώδικα ελέγχεται η αν είναι αληθής η λογική συνθήκη x > y. Αν είναι, τότε εκτελούνται οι εντολές των γραμμών 2 και 3 και στη συνέχεια οι εντολές από τη γραμμή 5 και μετά. Αν η συνθήκη x > y δεν αληθεύει τότε οι εντολές των γραμμών 2 και 3 δεν εκτελούνται και η εκτέλεση συνεχίζει από τη γραμμή 5 και έπειτα. Η λέξη endif σηματοδοτεί το τέλος των εντολών που εκτελούνται στην περίπτωση που αληθεύει η συνθήκη. 4
Algorithm 6 Εκτέλεση υπό συνθήκη (δεύτερη μορφή) 1: if x > y then 2: x x 1 3: else 4: y y 1 5: end if 6:... Οι εντολές που μεσολαβούν από το if μέχρι το endif ονομάζονται σώμα (body) του if. Η παραπάνω μορφή της εντολής if λειτουργεί ως εξής: στην περίπτωση που η συνθήκη του if είναι αληθής, εκτελούνται οι γραμμές από το if μέχρι το else. Στην αντίθετη περίπτωση εκτελούνται οι γραμμές από το else μέχρι το endif. Και στις δύο περιπτώσεις η εκτέλεση συνεχίζεται από τη γραμμή αμέσως μετά το endif. Algorithm 7 Εκτέλεση υπό συνθήκη (τρίτη μορφή) 1: if συνθήκη 1 then 2: x x 1 3: else if συνθήκη 2 then 4: x x + 1 5: else if... then 6:... 7: else 8: y y 1 9: end if 10:... Στην τρίτη και τελευταία μορφή της if ελέγχεται αν αληθεύει η συνθήκη του if (συνθήκη 1). Αν ναι, τότε εκτελούνται οι εντολές από το if μέχρι το επόμενο elseif. Αλλιώς, ελέγχεται η συνθήκη μετά το πρώτο elseif (συνθήκη 2). Αν είναι αληθής τότε εκτελούνται οι εντολές από το elseif μέχρι το επόμενο elseif κοκ. Η διαδικασία αυτή συνεχίζεται μέχρι η εκτέλεση να φτάσει στο endif ή στο else (αν αυτό υπάρχει). Η δομή που παρουσιάζεται παραπάνω είναι μια δομή επανάληψης. Είναι σύνηθες σε έναν αλγόριθμο κάποια βήματα να πρέπει να επαναληφθούν αρκετές φορές με κάποιες μικρές ίσως παραλλαγές. Όταν είναι γνωστός εκ των προτέρων ο αριθμός 5
Algorithm 8 Επανάληψη for 1: for i = 0 to 10 do 2: εντολή1 3:... 4: end for των επαναλήψεων που θα πραγματοποιηθούν χρησιμοποιείται η παραπάνω δομή επανάληψης. Οι εντολές που βρίσκονται από τη for μέχρι την endfor επαναλαμβάνονται όσες φορές καθορίζεται από το i = 0 to 10, δηλαδή μία φορά για i ίσο με 0, μία φορά για i ίσο με 1, κοκ. μέχρι το i να πάρει την τιμή 10 οπότε πραγματοποιείται η τελευταία επανάληψη. Η μεταβλητή i λέγεται μεταβλητή ελέγχου (control variable). Στο συγκεκριμένο παράδειγμα η μεταβλητή ελέγχου παίρνει όλες τις ακέραιες τιμές από την αρχική (0) μέχρι την τελική (10). Είναι δυνατό να ορίσουμε ένα διαφορετικό βήμα για να δηλώσουμε ότι η μεταβλητή ελέγχου δε θα αυξάνεται κατά 1 σε κάθε επανάληψη. Π.χ. μία for της μορφής for i = 0 to 10 step 2 θα έδινε στη μεταβλητή i τις τιμές 0, 2, 4,..., 10 ενώ η for i = 12 to 0 step 3 τις τιμές 12, 9, 6, 3, 0. Τέτοιου είδους επαναλαμβανόμενα σχήματα ονόμαζόνται βρόχοι (loops). Ο συγκεκριμένος βρόχος λέγεται βρόχος-for (for-loop) επειδή ελέγχεται από μία εντολή for. Algorithm 9 Επανάληψη while-do 1: while λογική συνθήκη do 2: εντολή1 3:... 4: end while 5:... Σε κάποιες περιπτώσεις δεν είναι εφικτό να ξέρουμε εκ των προτέρων πόσες επαναλήψεις θα πραγματοποιηθούν αλλά μπορεί να θέλουμε οι επαναλήψεις να πραγματοποιούνται αν και μόνο αν ισχύει μία συνθήκη. Στο παραπάνω παράδειγμα ένας εικονικός υπολογιστής όταν θα έφτανε στη γραμμή 1, θα υπολόγιζε την τιμή της λογικής συνθήκης. Αν αυτή ίσχυε, θα προχωρούσε στην εκτέλεση των εντολών στο σώμα του while και μετά θα επέστρεφε στη γραμμή 1 για να ξαναϋπολογίσει τη λογική συνθήκη. Η διαδικασία επαναλαμβάνεται μέχρι η λογική συνθήκη να αποτιμηθεί ως ψευδής. Σε αυτήν την περίπτωση η εκτέλεση συνεχίζει από την εντολή αμέσως μετά το σώμα του while στη γραμμή 5. Το παραπάνω σχήμα λέγεται και βρόχος-while (while-loop). Καλό 6
είναι να προσέξει κανείς ότι είναι πιθανό το σώμα ενός τέτοιου βρόχου να μην εκτελεστεί καθόλου. Αυτό συμβαίνει αν την πρώτη φορά που υπολογιστεί η λογική συνθήκη, αποτιμηθεί ως ψευδής. Επίσης είναι πιθανό ο βρόχος να μην τερματιστεί ποτέ (ατέρμων βρόχος). Φυσικά αυτό δεν είναι επιθυμητό καθώς όπως έχουμε ήδη αναφέρει βασικό χαρακτηριστικό των αλγορίθμων είναι ότι πρέπει να τερματίζουν¹. Μια παραλλαγή του βρόχου-while είναι ο βρόχος repeat-until στον οποίο η λογική συνθήκη εκτιμάται μετά την εκτέλεση του σώματος. Εδώ, εκτελούνται Algorithm 10 Επανάληψη repeat-until 1: repeat 2: εντολή1 3:... 4: until λογική συνθήκη 5:... κανονικά οι εντολές του σώματος του βρόχου (από τη γραμμή 2 μέχρι το until) και μετά εκτιμάται η λογική συνθήκη. Αν είναι αληθής τότε ξαναεκτελούνται οι εντολές του σώματος κοκ. Αν είναι ψευδής, η εκτέλεση συνεχίζεται από την εντολή αμέσως μετά το until (γραμμή 5). ¹Παρόλα αυτά θα δούμε ότι φαινομενικά ατέρμονες βρόχοι χρησιμοποιούνται στον προγραμματισμό αλλά σε συνδυασμό με εντολές που μπορούν να σπάνε το βρόχο 7