ΕΝΟΤΗΤΑ 9 ΕΝΩΣΗ ΞΕΝΩΝ ΣΥΝΟΛΩΝ ( ΟΜΕΣ UNION-FIND)
Ένωση Ξένων Συνόλων (Disjoint Sets with Union) S 1,, S k : ξένα υποσύνολα ενός συνόλου U δηλ., S i S j =, αν i j, και S 1 S k = U. Λειτουργίες που θέλουµε να υποστηρίζονται: MakeSet(x): επιστρέφει ένα νέο σύνολο που περιέχει µόνο το στοιχείο x. Union(S,T): επιστρέφει το σύνολο S T (τα S και T καταστρέφονται). Find(x): επιστρέφει το όνοµα του συνόλου S στο οποίο ανήκει το στοιχείο x. Παρατηρήσεις: n (διαφορετικές) MakeSet( ) U = n (δηλ., n στοιχεία συµµετέχουν στα σύνολα) Εάν n στοιχεία συµµετέχουν στα σύνολα, θα γίνουν το πολύ n-1 κλήσεις της Union( ). 2
Υλοποίηση µε Λίστες Κάθε σύνολο αναπαρίσταται από µια απλά συνδεδεµένη λίστα στοιχείων κάθε στοιχείο έχει δείκτη στο 1ο στοιχείο της λίστας (= αντιπρόσωπος) Έτσι: MakeSet(x): κατασκεύασε και επίστρεψε λίστα µε 1 κόµβο που περιέχει το x και έχει δείκτη-αντιπροσώπου στον εαυτό του Ο(1) Find(x): ακολουθούµε δείκτη-αντιπροσώπου από κόµβο που περιέχει x επιστρέφουµε το στοιχείο στον αντιπρόσωπο Ο(1) Union(S,T): σύνδεσε τους κόµβους της λίστας του T στη λίστα του S ενηµέρωσε τους δείκτες-αντιπροσώπου των κόµβων του T να δείχνουν στον αντιπρόσωπο του S (= αντιπρόσωπος του νέου συνόλου) Ο( T ) 3
Υλοποίηση µε Λίστες Βελτίωση της Πολυπλοκότητας της Union Για n στοιχεία, το κόστος όλων των Union() µπορεί να φθάσει και Θ(n 2 ). Πότε? Βελτιώνουµε την πολυπλοκότητα της Union() εάν πάντα συνδέουµε τη λίστα του µικρότερου συνόλου στο τέλος της λίστας του µεγαλύτερου και ενηµερώνουµε τους δείκτες-αντιπροσώπου του µικρότερου συνόλου. Θα πρέπει βέβαια να αποθηκεύουµε και να ενηµερώνουµε το µέγεθος κάθε συνόλου (σε κατάλληλο κόµβο-κεφαλή που τώρα παίζει και τον ρόλο αντιπροσώπου του συνόλου). Τότε, MakeSet(x) Union(S,T) Find(x) Ο(1) Ο(min{ S, T }) Ο(1) 4
Υλοποίηση µε Λίστες Θεώρηµα n MakeSet() και m Find() και Union() απαιτούν Ο(n + m + n logn) = O(m + n logn) χρόνο. Απόδειξη Οι n MakeSet() απαιτούν Ο(n) χρόνο. Οι Ο(m) Find() απαιτούν Ο(m) χρόνο. Οι Union() απαιτούν συνολικά O(n logn) χρόνο. Το κόστος της εκτέλεσης µιας Union() είναι ανάλογο του µεγέθους του µικρότερου από τα δύο σύνολα που συνενώνονται, και άρα µπορεί να επιµεριστεί χρεώνοντας Ο(1) κάθε στοιχείο του µικρότερου συνόλου. Για καθένα από τα O(n) στοιχεία: 1 η χρέωση σύνολο µεγέθους 2 2 η χρέωση σύνολο µεγέθους 4... k η χρέωση σύνολο µεγέθους 2 k Καθώς το µέγιστο µέγεθος συνόλου είναι n, κάθε στοιχείο θα συµµετέχει σε το πολύ logn κλήσεις της Union(). συνολικό κόστος Union() = O(n logn) 5
Υλοποίηση µε ένδρα Ανοδικό ένδρο (up-tree) δένδρο στο οποίο κάθε κόµβος διατηρεί µόνο έναν δείκτη στον πατέρα του (έτσι όλοι οι δείκτες δείχνουν προς τα επάνω). Ένας κόµβος µπορεί να έχει οποιοδήποτε πλήθος παιδιών. Το σύνολο U είναι ένα δάσος από ανοδικά δένδρα. Κάθε τέτοιο δένδρο περιέχει τα στοιχεία ενός από τα ξένα σύνολα. Το στοιχείο της ρίζας παίζει και το ρόλο αντιπροσώπου για το σύνολο. 6
Υλοποίηση Λειτουργιών Find(x): Ακολούθησε τους δείκτες από τον κόµβο που καταχωρεί το x προς τα επάνω ως τη ρίζα. Επίστρεψε στοιχείο ρίζας (αντιπρόσωπος). Έτσι: x, y U ανήκουν στο ίδιο σύνολο αν και µόνο αν Find(x) = Find(y). Union(S,T): Κάνε τη ρίζα του ενός δένδρου να δείχνει στη ρίζα του άλλου. ή Ποιες είναι οι Πολυπλοκότητες Χρόνου της Find( ) και της Union( )? Π.χ., αν έχουµε n σύνολα καθένα µε ένα στοιχείο, ποιο είναι το χειρότερο ύψος δένδρου που µπορούµε να πάρουµε και πως επιτυγχάνεται? 7
Μείωση ύψους δένδρου Θέλουµε τα δένδρα να µην έχουν µεγάλο ύψος. Για αυτό, θα πρέπει να είµαστε πολύ προσεκτικοί µε το πως συνδέουµε τα δένδρα στην Union( ). Συνένωση κατά τάξη (µέγεθος) (union by rank) Κάνουµε τη ρίζα του δένδρου µικρότερης τάξης να δείχνει στη ρίζα του δένδρου µεγαλύτερης τάξης, και όχι αντίστροφα. Η Τάξη ενός συνόλου: όταν κατασκευάζεται ένα σύνολο µε ένα στοιχείο (MakeSet()), η τάξη του είναι 0. όταν συνενώνονται δύο σύνολα διαφορετικών τάξεων, η τάξη της ρίζας δεν αλλάζει. όταν συνενώνονται δύο σύνολα ίδιας τάξης, η τάξη της ρίζας αυξάνει κατά 1. 8
Μείωση ύψους δένδρου Συµπίεση Μονοπατιού (path compression) Κατά τη διάρκεια εκτέλεσης κάθε Find(x), κάνε τον κάθε κόµβο στο µονοπάτι που διατρέχεις από τον κόµβο µε κλειδί x στη ρίζα να δείχνει στη ρίζα. Find(D) 9
Συµπίεση Μονοπατιού Εξ αιτίας της συµπίεσης µονοπατιού, η Find( ) εκτελείται κατά µέσο όρο (επιµερισµένη πολυπλοκότητα) σε σχεδόν σταθερό χρόνο. Συγκεκριµένα: όταν εφαρµόζεται η συνένωση κατά τάξη και η συµπίεση µονοπατιού, οποιαδήποτε ακολουθία από n MakeSet( ) και m Find( ) και Union( ) εκτελείται σε O(n + m α(m,m)) χρόνο όπου α(m,m) είναι η αντίστροφη της συνάρτησης του Ackermann και είναι α(m,m) log*m όπου log*m = πλήθος φορών που πρέπει να εφαρµόσουµε τον log 2 στο m ώστε να πάρουµε τιµή 1, π.χ., log 2 2 = log 2 2 = 1 log*2 = 1 log 2 4 = log 2 2 2 = 2 log*4 = 2 log 2 16 = log 2 2 4 = 4 log*16 = 3 log 2 65536 = log 2 2 16 = 16 log*65536 = 4 log 2 10 19728 = log 2 2 65536 = 65536 log*2 65536 = 5 (οι τιµές της log*m παραµένουν µικρές ακόµη και για πολύ µεγάλες τιµές του m --- log*m 5 για οποιαδήποτε «χρήσιµη» τιµή του m) 10
Εύρεση κόµβου µε κλειδί x Ερώτηση: Πως βρίσκουµε τον κόµβο µε κλειδί x στο δένδρο για το σύνολο που περιέχει το x? (δηλ., η Find(x) εµπεριέχει µια LookUp(). Πως θα υλοποιήσουµε αυτή τη LookUp?) 1η Περίπτωση Ο χώρος των κλειδιών είναι µικρός (π.χ. έχω n =100 κλειδιά συνολικά) ή τα κλειδιά είναι οι ακέραιοι 1, 2,, n: ιατηρούµε πίνακα µεγέθους n όπου η i-οστή θέση του πίνακα περιέχει δείκτη στο κόµβο του δένδρου µε κλειδί i Έτσι, η LookUp υλοποιείται σε σταθερό χρόνο. 11
Εύρεση κόµβου µε κλειδί x 2η Περίπτωση Ο χώρος των κλειδιών είναι µεγάλος: Χρησιµοποιούµε µια βοηθητική δενδρική δοµή (από αυτές που υλοποιούν λεξικά) που κάθε ένας κόµβος της δείχνει σε ένα από τα κλειδιά. Ποια είναι η πολυπλοκότητα της Find(x) σε αυτήν την περίπτωση? 12