(3 ο ) Εξαντλητική αναζήτηση I: μεταθέσεις & υποσύνολα (4 o ) Εξαντλητική αναζήτηση II: συνδυασμοί, διατάξεις & διαμερίσεις Είναι πράγματι τα «προβλήματα» τόσο δύσκολα; Είδαμε (σύντομα) στα προηγούμενα κεφάλαια την μορφή «δίδεται ζητείται ώστε» που λαμβάνουν όσα προβλήματα θα θέλαμε (και ελπίζουμε) να έχουν αλγοριθμική λύση. Μια ματιά σε αυτά τα προβλήματα μας αποκαλύπτει όμως ότι υπάρχει μια ευθεία «μέθοδος» για την επίλυσή τους: απλά, εξετάζουμε την μορφή της λύσης, παράγουμε όλες τις ενδεχόμενες λύσεις και δοκιμάζουμε αυτές μίαμία έως ότου βρούμε αυτήν που θεωρείται κατάλληλη (σύμφωνα με την περιγραφή του προβλήματος). Αυτή η ιδέα δεν μπορεί να μείνει ανεξέταστη καί πράγματι θα δούμε τρείς τουλάχιστον λόγους για τους οποίους πρέπει να την έχουμε έστω στο «πίσω μέρος» της σκέψης μας, αν και θα δούμε και έναν τουλάχιστον λόγο για τον οποίο δεν μπορούμε να ελπίσουμε και τα πάντα από αυτήν. Ας ανακαλέσουμε το πρόβλημα της «ΒΡΑΧΥΤΕΡΟΣ ΚΥΚΛΟΣ HAMILTON»: ΔΙΔΕΤΑΙ: ΖΗΤΕΙΤΑΙ: ΩΣΤΕ: «ΒΡΑΧΥΤΕΡΟΣ ΚΥΚΛΟΣ HAMILTON» Ένα σύνολο V «τοποθεσιών», και ένα σύνολο Ε διατεταγμένων ζευγών (κ, λ) τα οποία συνδέονται με «κατ ευθείαν» διαδρομή μήκους δ(κ, λ), Μια ακολουθία γ που περιέχει όλες τις τοποθεσίες u1, u2,..., u V α) οι διαδοχικές τοποθεσίες uκ 1 uκ, να συνδέονται μεταξύ τους κυκλικά, (δηλαδή και η τελευταία με την πρώτη). β) να περιέχει τις τοποθεσίες από ακριβώς μία φορά την κάθε μία. V 1 γ) το συνολικό μήκος της γ = δ( u, 1 k uk 1 ) δ( u V, u1) k = + +, να είναι το ελάχιστο δυνατόν. Αυτό που ζητά το παραπάνω πρόβλημα είναι μια ειδική ακολουθία τοποθεσιών, u1, u2,..., u V. Όλες αυτές όμως οι ακολουθίες είναι προφανώς πεπερασμένου πλήθους: είναι όσες οι μεταθέσεις Ν αριθμών, όπου Ν = V. Θα μπορούσαμε, επομένως, έστω και κατ αρχήν, να παραγάγουμε μία μία όλες τις μεταθέσεις των N τοποθεσιών και, απορρίπτοντας όσες δεν αποτελούν κύκλο, να διαλέξουμε την καλύτερη από αυτές. Παραγωγή όλων των μεταθέσεων: Το ερώτημα είναι λοιπόν: μπορούμε να παράγουμε όλες τις μεταθέσεις ν αριθμών και πώς; Η απάντηση είναι «ναί» και η σχετική ιδέα είναι η εξής: εάν βάλουμε όλες τις μεταθέσεις σε μια «σειρά», σε μια γραμμική διάταξη δηλαδή, τότε αρκεί να πάρουμε την 1 η από αυτές, και να βρούμε ένα τρόπο ώστε από κάθε μία μετάθεση να βρίσκουμε την αμέσως επόμενη, σε αυτή την διάταξη. Είναι προφανές πώς έτσι θα μπορούσαμε να απαριθμήσουμε όλες τις μεταθέσεις ν στοιχείων. Για να βρούμε μια τέτοια διάταξη καλόν είναι να αρχίσουμε από ένα τρόπο παράστασης μιας μετάθεσης ν στοιχείων: αριθμούμε τα στοιχεία (αυθαίρετα) με τους αριθμούς 1, 2,..., ν, και παριστούμε την μετάθεση όπου στην κ θέση μεταβαίνει το π(κ) στοιχείο με την ακολουθία π(1), π(2),..., π(ν). Η ακολουθία 3, 2, 5, 1, 4 παριστάνει την μετάθεση όπου 1 ο στοιχείο είναι το 3, 2 ο το 2, 3 ο το 5 κοκ. Δύο μεταθέσεις μπορούν τότε να συγκριθούν «λεξικογραφικά»: όπως π.χ. η λέξη «αυγό» είναι στο λεξικό (αρκετά) πριν από την λέξη «αυτό», έτσι μπορούμε να πούμε ότι η μετάθεση 3, 2, 4, 1, 5 είναι πρίν την μετάθεση 3, 2, 5, 1, 4, διότι οι δύο ακολουθίες μετά το αρχικό κοινό τμήμα 3, 2,... η μεν 1 η συνεχίζει με 4 ενώ η 2 η με μεγαλύτερο αριθμό, τον 5. Σε αυτή την διάταξη ξέρουμε ποια μετάθεσή έρχεται πρώτη (η 1, 2,..., ν 1, ν ), και ποιά τελευταία, (η ν, ν 1,..., 2, 1 ). Αλλά δεδομένης μιας μετάθεσης ποια είναι η λεξικογραφικά αμέσως επόμενη λ.χ. για ν = 7, ποια είναι η αμέσως επόμενη της 3, 2, 5, 7, 6, 4, 1 ; Παρατηρούμε εδώ ότι επειδή το το τελευταίο τμήμα..., 4, 1 είναι φθίνον δεν έχει μεγαλύτερο λεξικογραφικό (με τα ίδια στοιχεία). Το ίδιο και το..., 6, 4, 1, κοκ και μόνο με το τμήμα..., 5, 7, 6, 4, 1 μπορούμε να περιμένουμε να φτιάξουμε ένα μεγαλύτερο: στη θέση του 5 μπορούμε να Πανεπιστήμιο Κρήτης Τμήμα Επιστήμης Υπολογιστών Γ.Φ. Γεωργακόπουλος ver: 20/3/2012 1
βάλουμε το αμέσως μεγαλύτερο που έχει δεξιά του, δηλαδή το 6, και βέβαια να γράψουμε τα υπόλοιπα στοιχεία 7, 5, 4, 1 κατά αύξουσα σειρά, 3, 2, 6, 1, 4, 5, 7. Η μέθοδός μας έχει λοιπόν ως εξής: Μεταθέσεις «Πρώτη» Για κ = 1 έως ν θέτουμε π(κ) = κ. «Επόμενη» Βρίσκουμε το μεγαλύτερο φθίνον τελικό τμήμα: π(φ) < π(φ+1) > π(φ+2) >... > π(ν) Βρίσκουμε το αμέσως μεγαλύτερο του π(φ), έστω π(μ), στις θέσεις φ+1, φ+2,..., ν Εναλλάσουμε π(φ) και π(μ) Αντιστρέφουμε το τμήμα π(φ+1) π(φ+2)... π(ν) Τελευταίο? Εάν το σημείο «στροφής» φ είναι το 0. Ας δούμε ένα άλλο διαβόητο πρόβλημα, εκείνο της «ΑΠΛΗΣΤΟΥ ΣΑΚΚΟΥ»: «ΑΠΛΗΣΤΟΣ ΣΑΚΚΟΣ» ΔΙΔΕΤΑΙ: α) Ένα σύνολο Ν αντικειμένων, έκαστο με αξία ακ, και βάρος V «τοποθεσιών», και ένα σύνολο Ε διατεταγμένων ζευγών (κ,λ) τα οποία συνδέονται με «κατ ευθείαν» διαδρομή μήκους δ(κ,λ), β) Δύο όρια, ένα κάτω όριο αξίας Α, και ένα πάνω όριο βάρους. ΖΗΤΕΙΤΑΙ: Ένα (υπο)σύνολο των αντικειμένων S {1, 2,..., N} ΩΣΤΕ: α) Η συνολική αξία α κ S κ να είναι τουλάχιστον Α. β) Το συνολικό βάρος κ να είναι το πολύ Β. (= να αντέχει το «σακκούλι»...) β κ S Αυτό που ζητά το παραπάνω πρόβλημα είναι ένα υποσύνολο από ένα σύνολο Ν στοιχείων, και γνωρίζουμε ότι όλα τα δυνατά υποσύνολο είναι πεπερασμένου πλήθους. Θα μπορούσαμε λοιπόν να παραγάγουμε ένα προς ένα όλα τα δυνατά υποσύνολα, και να ελέγξουμε εάν έστω ένα εξ αυτών πληροί τις προϋποθέσεις αξίας βάρους που τίθενται στον ορισμό του προβλήματος. Παραγωγή όλων των υποσυνόλων ενός συνόλου με ν στοιχεία συναρτήσεις {1..ν} {1..μ}: Το νεό ερώτημα είναι: μπορούμε να παράγουμε όλα τα υποσύνολα ενός συνόλου με ν στοιχεία; Και εδώ η απάντηση είναι «ναί» και η σχετική ιδέα έχει, μάλιστα, γενικότερη αξία. Αριθμούμε αυθαίρετα τα στοιχεία μας με τους αριθμούς 0...ν 1. Κάθε σύνολο μπορεί λοιπόν να παρασταθεί με ένα πίνακα τ[0..ν 1], ν τιμών, όπου οι τιμές αυτές είναι 0..1: μπορούμε να ερμηνεύσουμε το τ(κ)=«0» ως «το κ στοιχείο εξαιρείται του υποσυνόλου S» και το τ(κ)= «1» ως «το κ στοιχείο περιλαμβάνεται στο υποσύνολο S». Κάθε συνάρτηση {0,..., ν 1} {0, 1} γίνεται λοιπόν ένα υποσύνολο (και αντιστρόφως, κατά προφανή τρόπο). Πώς μπορούμε λοιπόν να λάβουμε όλες τις συναρτήσεις {0,..., ν 1} {0, 1}; Μια τεχνική που μας λύνει το πρόβλημα είναι το «οδομετρικό» τέχνασμα, το οποίο μας δίνει γενικότερα όλες τις συναρτήσεις {0,..., ν 1} {0, 1,..., μ 1}: Ερμηνεύουμε τον πίνακα τ[0...ν 1] ως τα ψηφία ενός αριθμού γραμμένου σε βάση μ, και απλά προσθέτουμε σε αυτόν διαρκώς τον αριθμό 1 με τον τρόπο που από μικρή ηλικία μαθαίνουμε: αν προσθέσουμε το 1 και περάσουμε την «βάση» έχουμε 1 «κρατούμενο» που προσθέτουμε στο στο αμέσως σημαντικότερο ψηφίο. Μετά από πρόσθεση της μονάδας, ο πίνακας μας τ( ) θα παριστά άλλη μια συνάρτηση κ τ(κ) από το {0..ν 1} στο {0..μ 1}, (ή, ισοδυνάμως {1..ν} {1..μ}). Υποσύνολα «Πρώτο» Για κ = 1 έως ν θέτουμε τ(κ) = 0. «Επόμενo» ψ = 0 Επαναλαμβάνουμε τ(ψ) (τ(ψ)+1) mod μ, ΟΚ = (τ(ψ)>0), ψ ψ+1 } έως ΟΚ ή (ψ=ν) Τελευταίο? Όταν φθάσουμε στο ψ=ν Αν εφαρμόσετε αυτό, π.χ., με ν = 4 ψηφία και βάση μ = 2, θα πάρετε τους 16 δυαδικούς αριθμούς 00..15 σε αύξουσα σειρά, τους οποίους μπορείτε εύκολα να ερμηνεύσετε ως (όλα) τα υποσύνολα του {0, 1, 2, 3}: Πανεπιστήμιο Κρήτης Τμήμα Επιστήμης Υπολογιστών Γ.Φ. Γεωργακόπουλος ver: 20/3/2012 2
τ [3 2 1 0] S τ [3 2 1 0] S τ [3 2 1 0] S τ [3 2 1 0] S 0 0 0 0 0 1 0 0 { 2 } 1 0 0 0 { 3 } 1 1 0 0 { 2, 3 } 0 0 0 1 { 0 } 0 1 0 1 { 0, 2 } 1 0 0 1 { 0, 3 } 1 1 0 1 { 0, 2, 3 } 0 0 1 0 { 1 } 0 1 1 0 { 1, 2 } 1 0 1 0 { 1, 3 } 1 1 1 0 { 1, 2, 3 } 0 0 1 1 { 0, 1 } 0 1 1 1 { 0, 1, 2 } 1 0 1 1 { 0, 1, 3 } 1 1 1 1 { 0, 1, 2, 3 } Το εξής επόμενο πρόβλημα είναι από μεγάλη κατηγορία προβλημάτων με εξαιρετικό πρακτικό αντίκτυπο. Δίνουμε μια σχετικά απλοποιημένη εκδοχή του: ΔΙΔΕΤΑΙ: ΖΗΤΕΙΤΑΙ: ΩΣΤΕ: «ΔΡΟΜΟΛΟΓΗΣΗ ΕΡΓΑΣΙΩΝ» α) Ένα σύνολο V «εργασιών» μοναδιαίας χρονικής διάρκειας β) Μια μερική διάταξη «προτεραιοτήτων» μεταξύ τους. γ) Ένας πίνακας «χρονο δρομολόγησης» με P T θέσεις (δείτε το Ρ σαν πλήθος «επεξεργαστών» ή «μηχανών», και το T σαν πλήθος χρονικών βημάτων). Μια ανάθεση της κάθε εργασίας i V σε μια θέση (p(i), t(i)) του πίνακα δρομολόγησης, α) Διαφορετικές εργασίες ανατίθενται σε διαφορετικές μηχανές: i j p(i) p(j). β) Εάν μια εργασία «προαπαιτείται» για μια άλλη τότε η 1 η πρέπει να ανατεθεί σε χρονική στιγμή πρίν την 2 η : i j t(i) < t(j). Τί θα μπορούσαμε να κάνουμε για να δοκιμάσουμε όλους τους δυνατούς τρόπους να δρομολογήσουμε τις εργασίες μας; Θα μπορούσαμε φυσικά να δοκιμάσουμε όλες τις συναρτήσεις p: {1...N} {1..P} επί όλες τις συναρτήσεις αλλά μια (εκ πρώτης όψεως) καλύτερη ιδέα είναι η εξής: κάποιες εργασίες θα πρέπει να γίνουν στο 1 ο βήμα. Και βέβαια αυτές θα πρέπει να είναι κάποιες p (το πολύ) από όσες δεν έχουν καμμιά άλλη εργασία ως προϋπόθεση. Εξετάζουμε λοιπόν όλες τις εργασίες, επιλέγουμε όσες Ε από αυτές δεν έχουν καμμιά άλλη ως προϋπόθεση, και εξετάζουμε ως εργασίες της 1 ης χρονικής στιγμής όλους του συνδυασμούς C, Ε εργασιών ανά p. Για να δούμε πως θα συνεχίσουμε, απλά επαναλαμβάνουμε το αυτό για τις υπόλοιπες εργασίες V C. Εάν συμβολίσουμε με opt(v,p) το ελάχιστο πλήθος βημάτων που αρκεί για να εκτελέσουμε σωστά τις V με p μηχανές, ισχύει ο εξής (αναδρομικός) τύπος: opt(v,p) = 1 + min{ opt(v C) : Ε οι απροϋπόθετες εργασίες του V, και C συνδυασμός «των Ε ανά p», } Παραγωγή όλων των συνδυασμών ν στοιχεία ανά κ: Το επόμενο λοιπόν ερώτημά μας είναι: μπορούμε (και πώς;) να παράγουμε όλους τους συνδυασμούς ν στοιχείων ανά κ; Η απάντηση είναι και πάλι «ναί», και μάλιστα με το ίδιο τέχνασμα που χρησιμοποιήσαμε για τις μεταθέσεις την λεξικογραφική διάταξη. Αριθμούμε τα στοιχεία μαςαυθαίρετα με 1 έως ν, και χρησιμοποιούμε ένα πίνακα κ δεικτων δ(1..κ) για παραστήσουμε τον συνδυασμό {δ(1), δ(2),..., δ(κ)} {1..ν}. Είναι προφανές πως ο μικρότερος και πρώτος λεξικογραφικά συνδυασμός είναι ο 1, 2, 3,..., κ. Προσέξτε ότι θεωρούμε τα στοιχεία διατεταγμένα κατά την αύξουσα σειρά, κάτι που επιτρέπεται να το κάνουμε διότι η πρόκειται περί συνδυασμών όχι διατάξεων: η σειρά δεν παίζει ρόλο και επομένως μπορεί να είναι όποια μας βολεύει. Αλλά πώς λαμβάνουμε τώρα τον αμέσως επόμενο λεξικογραφικά συνδυασμό; Π.χ. για ν = 7 και κ = 4, ποιός είναι ο αμέσως επόμενος του συνδυασμού 1, 3, 4, 6 ; Προφανώς αρκεί να αυξήσουμε κατά 1 το δεξιότερο στοιχείο, δηλαδή το 6, οπότε λαμβάνουμε το 1, 3, 4, 7. Αλλά ποιός είναι τώρα ο επόμενος αφού το 7 δεν μπορεί να αυξηθει άλλο; Πρέπει να αναζητήσουμε λοιπόν το πρώτο εκ δεξιών στοιχείο που μπορεί να αυξηθεί κατά 1, (εδώ το 4), και να συνεχίσουμε με τον μικρότερο συνδυασμό των στοιχείων που απομένουν, ο οποίος είναι να τα πάρουμε κατά αύξουσα σειρά. Άρα μετά τον 1, 3, 4, 6 έρχονται κατά λεξικογραφική σειρά οι συνδυασμοί: 1, 3, 4, 6 1, 3, 4, 7 1, 3, 5, 6 1, 3, 5, 7 1, 3, 6, 7 1, 4, 5, 6 1, 4, 5, 7... Συνδυασμοί «Πρώτος» Για δ = 1 έως κ θέτουμε σ(δ) = δ. Χρησιμοποιούμε βοηθητικά ότι σ(κ+1) = (ν+1). «Επόμενoς» Έστω δ ο πρώτος «δείκτης» εκ δεξιών που μπορεί να αυξηθεί, δηλαδή «σ(δ)+1 < σ(δ+1)» Αυξάνουμε το σ(δ) κατά 1, και, Θέτουμε τους υπόλοιπους στις επόμενες θέσεις: για i = δ+1 έως κ { σ(i) σ(δ)+(i δ) } Πανεπιστήμιο Κρήτης Τμήμα Επιστήμης Υπολογιστών Γ.Φ. Γεωργακόπουλος ver: 20/3/2012 3
Τελευταίος? Όταν δ = 0 (= κανείς δείκτης δεν μπορεί να αυξηθεί) Παραθέτουμε στη συνέχεια την τεχνική που εξηγήσαμε, παριστάνοντας τους εκάστοτε συνδυασμούς ως κ = 4 δείκτες πάνω σε μια σειρά ν = 7 αριθμών: 1 2 3 4 5 6 7 ( 8 )..................... Για να συνεχίσουμε, ας θυμηθούμε το από (2 ο ) κεφάλαιο το πρόβλημα της «ΜΕΓΙΣΤΟΒΑΡΟΥΣ ΔΙΜΕΡΟΥΣ ΑΝΤΙΣΤΟΙΧΙΣΗΣ»: «ΠΡΟΤΙΜΟΤΕΡΗ/ΜΕΓΙΣΤΟΒΑΡΗΣ ΔΙΜΕΡΗΣ ΑΝΤΙΣΤΟΙΧΙΣΗ» ΔΙΔΕΤΑΙ: α) Ένα σύνολο V από «στοιχεία» αποτελούμενο από δύο μέρη Α και Β, (ξένα μεταξύ τους). β) Ένα σύνολο Ε Α Β, συσχετίσεων (κ, λ), από το 1 ο μέρος Α στο 2 ο μέρος Β. γ) Ένα βάρος («προτίμησης») w(α,β) Ν για κάθε συσχέτιση (α,β) Ε. ΖΗΤΕΙΤΑΙ: Ένα σύνολο συσχετίσεων, Μ Ε. ΩΣΤΕ: α) να είναι «ανεξάρτητες» μεταξύ τους, δηλαδή εάν (α, β), (α, β ) Μ, τότε α α, β β. β) το συνολικό βάρος w(μ) = w( αβ, ), να είναι το μέγιστο δυνατόν. ( αβ, ) M Αν κάποιες συσχετίσεις δεν υπάρχουν στο αρχικό σύνολο Ε, μπορούμε να τις συμπεριλάβουμε, αρκεί να τους δώσουμε βάρος μηδέν. Παρατηρείστε τώρα ότι οι συσχετίσεις που ζητάμε ανάμεσα στα δύο μέρη αποτελούν μια «ένθεση» του μικρότερου μέρους στο μεγαλύτερο: αν δηλαδή κ = A και ν = Β, και χωρίς απώλεια γενικότητας, υποθέσουμε ότι κ ν, τότε δεν ζητάμε παρά μια συνάρτηση δ: {1..κ} {1..ν}, η οποία να είναι ενθετική, δηλαδη (i j δ(i) δ(j)). Προφανώς θα θεωρήσουμε ότι το i στοιχείο θα συσχετιστεί με το δ(i). Τα στοιχεία όμως δ(1), δ(2),..., δ(n) δεν είναι παρά μια διάταξη ν στοιχείων ανά κ (κ ν) η σειρά εδώ προφανώς παίζει ρόλο. Δεν έχουμε λοιπόν, παρά να σχηματίσουμε όλες τις διατάξεις Δν,κ και από τις αντίστοιχες συσχετίσεις i δ(i), i = 1,...,κ, να διαλέξουμε εκείνη με το μεγαλύτερο συνολικό βάρος. Παραγωγή όλων των διατάξεων ν στοιχεία ανά κ: Mπορούμε να παράγουμε όλες τις διατάξεις ν στοιχείων ανά κ; Η απάντηση εδώ είναι, εύκολα πια, «ναί», διότι δεν έχουμε παρά να παράγουμε έναν έναν τους συνδυασμούς ανά κ, και για κάθε έναν από αυτούς να πάρουμε όλες τις μεταθέσεις των στοιχείων του. Το «όλες οι διατάξεις» ανάγεται απλά στο «όλοι οισυνδυασμοί» επί «όλες οι μεταθέσεις». Θα τελειώσουμε αυτή την δειγματοληπτική εξέταση επίλυσης προβλημάτων μεσω «εξαντλητικής αναζήτησης», με το πρόβλημα του χρωματισμού: «ΒΕΛΤΙΣΤΟΣ ΧΡΩΜΑΤΙΣΜΟΣ» ΔΙΔΕΤΑΙ: α) Ένα σύνολο V «στοιχείων», και ένα σύνολο Ε V V ζευγών συσχετίσεων (i, j). β) Έναν φυσικό k (δείτε τον ως πλήθος «χρωμάτων»). ΖΗΤΕΙΤΑΙ: Μια συνάρτηση χ: V {1,..., k} Πανεπιστήμιο Κρήτης Τμήμα Επιστήμης Υπολογιστών Γ.Φ. Γεωργακόπουλος ver: 20/3/2012 4
ΩΣΤΕ: Να αποδίδει διαφορετικές τιμές χ(i) («χρώματα») στα στοιχεία που σχετίζονται μεταξύ τους, δηλαδή εάν (i, j) Ε είναι συσχετισμένα, τότε θα πρέπει χ(i) χ(j). Τί ζητάει αυτό το πρόβλημα; Μια συνάρτηση από Ν = V στοιχεία, στους αριθμούς {1..k}, επομένως θα μπορούσαμε να εξετάσουμε μία προς μία τις συναρτήσεις {1..Ν} {1..k}, όπως έχουμε δεί προηγουμένως. Υπάρχει όμως και μια άλλη ενδιαφέρουσα οπτική γωνία, γενικότερα χρήσιμη: δεδομένης μια συνάρτησης σ: {1..Ν} {1..k}, το σύνολο {1..Ν} διαμερίζεται σε k υποσύνολα, τα σ 1 (1), σ 1 (2),..., σ 1 (k). (Αν κάποια από αυτά είναι κενό, τότε κάποιο χρώμα δεν χρησιμοποιείται, και θα μπορούσαμε να περάσουμε σε μικρότερο k.) Αρκεί λοιπόν να εξετάσουμε όλες τις διαμερίσεις Ν στοιχείων, να ελέγξουμε καθεμία εάν είναι αποδεκτή (δηλαδή εάν αποτελείται από ασυσχέτιστα στοιχεία), να χρωματίσουμε κάθε τμήμα της διαμέρισης με το ίδιο χρώμα, και να διαπιστώσουμε ποια αποδεκτή διαμέριση έχει τα λιγότερα τμήματα (εδώ = χρώματα). Παραγωγή όλων των διαμερίσεων ενός συνόλου με ν: Mπορούμε όμως να παράγουμε όλες τις διαμερίσεις ν στοιχείων, λ.χ των αριθμών {1, 2,..., ν}; Η απάντηση είναι και εδώ «ναί» όχι όμως τόσο εύκολα: θα χρησιμοποιήσουμε και πάλι μια διάταξη των διαμερίσεων (όπως στις μεταθέσεις και τους συνδυασμούς), αυτή την φορά όμως η διάταξή μας δεν θα είναι γραμμική αλλά δενδρική. Ας δούμε, κατ αρχάς, ως παράδειγμα, όλες τις 5 διαμερίσεις του συνόλου {1, 2, 3}: 1 η 2 η 3 η 4 η 5 η { 1, 2, 3 } { 1, 2 } { 3 } { 1, 3 } { 2 } { 1 } { 2, 3 } { 1 } { 2 } { 3 } Δεν είναι καν προφανές πως αυτές είναι σωστά όλες... αλλά όντως έτσι είναι. Ας προσπαθήσουμε από αυτές να παραγάγουμε τις διαμερίσεις ενός συνόλου με 4 στοιχεία, αρκεί του {1, 2, 3, 4}. Αν από μια διαμέριση του {1, 2, 3, 4}, λχ. από την { 1, 2, 4} { 3 }, διαγράψουμε το 4 θα μείνουμε προφανώς με μια διαμέριση του {1, 2, 3}, επομένως για να πάρουμε όλες τις διαμερίσεις για ν = 4, αρκεί να προσθέσουμε το 4 στις διαμερίσεις για ν = 3, με όλους τους δυνατούς τρόπους σε κάθε μία από αυτές. Αν μια διαμέριση όμως έχει κ τμήματα τότε «προσθέτω ένα ακόμα στοιχείο» σημαίνει ότι το προσθέτω είτε σε ένα από αυτά τα κ τμήματα, είτε μόνο του, ως το υπ.αρ. κ+1. Όλοι οι τρόποι να προσθέσουμε το 4 π.χ. στην διαμέριση { 1, 2, 4} { 3 } είναι οι τρείς: { 1, 2 } { 3 } { 1, 2, 4 } { 3 } { 1, 2 } { 3, 4 } { 1, 2 } { 3 } { 4 } Βλέπουμε ότι κατά φυσικό τρόπο μια διαμέριση τριών στοιχείων παράγει πολλές «επόμενες» διαμερίσεις με ένα στοιχείο επί πλέον. Για ν = 3, από την 1 η διαμέριση παίρνουμε 2 για ν = 4, από την 2 η παίρνουμε 3, από την 3 η και 4 η επίσης 3, και από την 5 η παίρνουμε 4. Δημιουργείται έτσι μια δενδρική διάταξη διαμερίσεων στη ρίζα της οποίας είναι η μία και μοναδική διαμέριση για ν = 1, η { 1 }. Στο 2 ο επίπεδο θα είχαμε τις { 1 2 } και { 1 } { 2 }, και στο τρίτο τις 5 παραπάνω διαμερίσεις, τις δύο πρώτες θυγατρικής της { 1, 2 }, και τις τρείς τελευταίες ως θυγατρικές της { 1 } { 2 }. Μπορούμε όμως να παραγάγουμε όλες τους κόμβους αυτού του δένδρου έως οποιαδήποτε επιθυμητό επίπεδο, (λ.χ. το ν = 4), στηριζόμενοι στις τεχνικές βηματικής διάνυσης των δένδρων (δείτε το μάθημα «Δομές δεδομένων»). Αυτό που μας αρκεί είναι να μπορούμε να γνωρίζουμε για κάθε κόμβο αυτού του δένδρου, δηλαδή για κάθε διαμέριση, να γνωρίζουμε (δηλαδή να μπορούμε να υπολογίζουμε) τρία πράγματα: τον πατρικό κόμβο από τον οποίο προέρχεται, τον 1 ο θυγατρικό που παράγει, και τον αμέσως επόμενο αδελφικό που έχει (αν έχει). Και τα τρία αυτά είναι εύκολα, όπως είδαμε στο παράδειγμά μας για το ν = 4. Διαμερίσεις «Πατρική» Βρίσκουμε τον μεγαλύτερο αριθμό της διαμέρισης και τον διαγράφουμε «Δίχως πατρικό?» Η διαμέρισή μας είναι η ριζική: { 1 } «Πρώτη θυγατρική» Βρίσκουμε τον μεγαλύτερο αριθμό της διαμέρισης έστω ν Προσθέτουμε τον (ν+1) στο πρώτο τμήμα της διαμέρισης που έχουμε «Δίχως 1 η θυγατρική?» Έχουμε φθάσει στο ν που επιθυμούμε «Επόμενη αδελφική» Βρίσκουμε τον μεγαλύτερο αριθμό της διαμέρισης έστω ν Διαγράφουμε το ν από το τρέχον τμήμα και τον εισάγουμε στο επόμενο Πανεπιστήμιο Κρήτης Τμήμα Επιστήμης Υπολογιστών Γ.Φ. Γεωργακόπουλος ver: 20/3/2012 5
«Δίχως αδελφική?» Ο μεγαλύτερος ν είναι μόνος του ως τελευταίο τμήμα της διαμέρισης Με τα παραπάνω διαθέσιμα είναι δυνατόν να διανύσουμε το δένδρο των διαμερίσεων μέχρι οποιοδήποτε επίπεδο χρειαζόμαστε. Η μέθοδος διάνυσης που δίνουμε (καθοδική, βηματικά) ισχύει για όλα τα δένδρα, αρχίζει από τον ριζικό κόμβο, και για τον εντοπισμό του «επόμενου» κόμβου κάνει τα εξής: Επόμενος ο 1 ος θυγατρικός Αν δεν υπάρχει τότε Επόμενος ο επόμενος αδελφικός, και Αν δεν υπάρχει, τότε Επόμενος ο επόμενος αδελφικός του πλησιέστερου πατρικού ή προπατρικού, κοκ, που έχει «αδελφό». Προσέξτε ότι διανύουμε όλο το δένδρο, και κατά συνέπεια, εάν θέλουμε τις διαμερίσεις ν στοιχείων, πρέπει να κινούμαστε μέχρι και το ν οστό επίπεδο, και να αναφέρουμε μόνον όσες διαμερίσεις συναντάμε σε αυτό. Είδαμε λοιπόν στα προηγούμενα πώς να χρησιμοποιούμε μερικά θεμελιακά συνδυαστικά αντικείμενα, (όπως μεταθέσεις n αντικειμένων, υποσύνολα n αντικειμένων και γενικότερα συναρτήσεις {1...n} {1..m}), συνδυασμοί ή διατάξεις n αντικειμένων ανά k και διαμερίσεις συνόλου n αντικειμένων), για την «εξαντλητική» παραγωγή όλων των ενδεχόμενων λύσεων διαφόρων ειδών προβλημάτων. Στην ιστορία της επιστήμης του υπολογισμού η εύρεση έστω και ενός τέτοιου είδους αλγορίθμου ήταν για πολλές δεκαετίες κάτι το επαρκές: κανείς δεν ενδιαφερόταν για κάτι περισσότερο, και δεν είχε λόγο για κάτι τέτοιο διότι απλά δεν υπήρχαν υπολογιστικές συσκευές και η όλη συζήτηση ήταν ούτως ή άλλως «θεωρητική». Μετά την κατασκευή και την αρχική ευρύτερη διάδοση των πρώτων υπολογιστικών συσκευών (δηλαδή μετά το 1960, αν και κάποιες είδους υποψίες είχαν εκφραστεί νωρίτερα), οι ερευνητές άρχισαν να ανησυχούν για το ότι η εξαντλητική μέθοδος, όσο μεθοδική και αν είναι, πρακτικώς είναι άχρηστη. Ο λόγος φανερώνεται εάν εξετάσουμε το πλήθος των σχετικών αντικειμένων: Είδος: Πλήθος: Μεταθέσεις ν αντικειμένων: ν! Υποσύνολα συνόλου ν αντικειμένων: γενικότερα, συναρτήσεις {1...ν} {1..μ}: 2 ν μ ν Συνδυασμοί ν αντικειμένων ανά κ: ν!/(κ! (ν κ)!) Διατάξεις ν αντικειμένων ανά κ: ν!/(ν κ)! Διαμερίσεις συνόλου ν αντικειμένων: τείνει σε > 4 ν Αυτά τα μεγέθη λαμβάνουν αστρονομικές τιμές ήδη για μικρές τιμές του ν ή και του κ. Χρησιμοποιώντας τον τύπο του Stirling ( n! 2 πn ( n/ e) n ) παίρνουμε ότι n! > (n/3) n, και ότι επομένως 30! > 10 30. Προς σύγκριση, ας μετρήσουμε πόσους κύκλους θα περιμέναμε να κάνει ένας ταχύτατος επεξεργαστής της εποχής μας (λ.χ. ένας στα 3.0 Ghz), ακόμα και εάν τον αφήναμε να έτρεχε για όλη την ζωή του συμπαντος, δηλαδή περί τα 10.000.000.000 χρόνια. Ένα έτος έχει λιγότερο από 32 10 6 δευτερόλεπτα, επομένως σε 10 10 χρόνια, με 3 10 9 hz θα είχαμε λιγότερο από 100 10 6 10 9 10 10 = 10 27 κύκλους, τουλάχιστον χίλιες φορές λιγότερους από το 30!: ο επεξεργαστής μας δεν θα είχε ελπίδα να εξετάσει όλες τις μεταθέσεις 30 αριθμών ακόμα και αν το σύμπαν ξαναζούσε 1000 φορές... Ανάλογα απελπιστικοί αριθμοί προκύπτουν και για τα άλλα συνδυαστικά αντικείμενα του παραπάνω πίνακα. Το «πεπερασμένο» είναι μεν τέτοιο, μπορεί όμως να είναι απίστευτα μεγάλο... Για πιό λόγο τότε πρέπει να έχουμε έστω στο «βάθος του μυαλού μας» την λεγόμενη εξαντλητική (sic) μέθοδο; Τουλάχιστον για τους εξής τρείς: 1. Σε πολλές περιπτώσεις, τόσο η χρήση της εξαντλητικής αναζήτησης (για μικρά στιγμιότυπα), όσο και η υλοποίηση της έχουν πρακτικό νόημα, ιδίως όταν επειγόμαστε ή θέλουμε απλά να πειραματιστούμε. Ας φανταστούμε το εξής δίλημμα: μπορούμε είτε να γράψουμε ένα απλό τεμάχιο κώδικα το οποίο όμως χρειάζεται ίσως και 10 ημέρες για να λύσει κάποια από τα Πανεπιστήμιο Κρήτης Τμήμα Επιστήμης Υπολογιστών Γ.Φ. Γεωργακόπουλος ver: 20/3/2012 6
στιγμιότυπα του προβλήματος που μας ενδιαφέρουν, είτε να αποδοθούμε σε μια ερευνητική περιπέτεια για να λύσουμε το πρόβλημά μας κατά ουσιωδώς καλύτερο τρόπο, διαδικασία όμως η οποία ίσως διαρκέσει μήνες ή και χρόνια. Δεν θα διαλέγαμε έστω προσωρινά την 1 η λύση; 2. Για πολλά προβλήματα και για πολλά με σοβαρό πρακτικό αντίκτυπο δεν διαθέτουμε ακόμα (2011) καμμία μέθοδο που να είναι ουσιωδώς δραστικότερη από εκείνης της εξαντλητικής αναζήτησης. Η ανάλυση αυτής της κατάστασης, και η λεπτομερής ταξινόμηση των προβλημάτων σε «εύκολα» και «δύσκολα» παραμένει ακόμα το περισσότερο σοβαρό και δυσκολότερα ανοικτό πρόβλημα της θεωρητικής πληροφορικής. 3. Τέλος, δεν πρέπει να αδιαφορούμε για μια αλήθεια θεωρητικής προέλευσης, με σοβαρό όμως πρακτικό αντίκτυπο: η θεωρία υπολογισμού μας έχει από καιρό ενημερώσει ότι δεν είναι κάθε πρόβλημα επιδεκτό αλγοριθμικής επίλυσης με άλλα λόγια υπάρχουν προβλήματα που δεν είναι δυνατόν να λυθούν αλγοριθμικά, όσο παράξενο, εκ πρώτης όψεως, αν φαίνεται αυτό. Η «πράξη» λοιπόν είναι σε θέση να μας προσφέρει προβλήματα παγίδες, προβλήματα για τα οποία εμείς θα ψάχνουμε ματαίως να βρούμε έναν αλγόριθμο ενώ αυτά δεν θα έχουν ούτε κάν ένα... Η απλούστερη, αλλά και αρκετά αποτελεσματική, μέθοδος που έχουμε για να αποφεύγουμε τέτοιες παγίδες είναι να εξετάζουμε, για κάθε καινούργιο πρόβλημα, το εάν έχει ή όχι έναν έστω εξαντλητικό αλγόριθμο. Σε μερικές περιπτώσεις αυτό τυχαίνει να είναι δύσκολο έργο, αλλά στην μεγάλη πλειοψηφία των περιπτώσεων θα μας είναι αρκετά εύκολο να ανακαλύπτουμε έναν «εξαντλητικό» αλγόριθμο. Συχνά αυτό θα είναι τόσο εύκολο ώστε να γίνεται πρακτικώς «ακαριαία» ή ακόμα και να το προσπερνάμε «σιωπηρά». Ανακεφαλαίωση και τα επόμενα βήματα: Είδαμε λοιπόν ότι τα κατ ευχήν αλγοριθμικά προβλήματα επιδέχονται μια απλοϊκή αλγοριθμική λύση την εξαντλητική αναζήτηση, είδαμε όμως ότι αυτή δεν μας προσφέρει την «πρακτικότητα» που αναμένουμε από μια αλγοριθμική λύση. Ήλθε λοιπόν η ώρα να εξετάσουμε το κεντρικό πυρηνικό ερώτημα της περιοχής της σχεδίασης και ανάλυσης αλγορίθμων: μπορούμε (;) και πώς (;) να σχεδιάσουμε πιο δραστικούς αλγορίθμους, αλγορίθμους που να αποφεύγουν τον ύφαλο της «εξαντλητικής αναζήτησης»; Για να αξιολογήσουμε όμως τους αλγορίθμους (σε περισσότερο ή λιγότερο δραστικούς) θα πρέπει πρώτα να μπορούμε να μετράμε τις «επιδόσεις» τους. Το ποιές είναι αυτές, πώς μπορούμε να τις μετρήσουμε και πώς μπορούμε να τις διατάξουμε σε καλύτερες και χειρότερες θα μας απασχολήσει στο επόμενο κεφάλαιο το τελευταίο «προπαρασκευαστικό» αυτών των σημειώσεων. Πανεπιστήμιο Κρήτης Τμήμα Επιστήμης Υπολογιστών Γ.Φ. Γεωργακόπουλος ver: 20/3/2012 7