ΕΝΟΤΗΤΑ 7 ΟΥΡΕΣ ΠΡΟΤΕΡΑΙΟΤΗΤΑΣ ΣΩΡΟΙ
Ουρές Προτεραιότητας (Priority Queues) Θεωρούµε ότι τα προς αποθήκευση στοιχεία έχουν κάποια διάταξη (καθένα έχει µια προτεραιότητα). Τα προς αποθήκευση στοιχεία είναι της µορφής <Κ,Ι> όπου: Κ: είναι το κλειδί (προτεραιότητα), τύπου key Ι: πληροφορία που συσχετίζεται µε αυτό το κλειδί, τύπου info. Σηµ.: Σε απλές περιπτώσεις, µπορεί <Κ,Ι> = <Κ>. Ουρά προτεραιότητας ένας αφηρηµένος τύπος δεδοµένων για ένα σύνολο µε στοιχεία <Κ,Ι>, που υποστηρίζει: MakeEmpty( ): επιστρέφει κενή δοµή. IsEmpty(S): επιστρέφει true αν S = κενή δοµή false διαφορετικά. Insert(K,I,S): εισάγει το ζεύγος <Κ,Ι> στο S. FindMin(S): επιστρέφει το στοιχείο <Κ,Ι> του S, όπου Κ είναι το µικρότερο κλειδί στο S. DeleteMin(S): διαγράφει το στοιχείο <Κ,Ι>, όπου Κ είναι το µικρότερο κλειδί στο S, και επιστρέφει <Κ,Ι>. Σηµ.: Σε κάποιες ουρές, µας ενδιαφέρουν οι FindMax( ) και DeleteMax( ) (αντί για FindMin( ) και DeleteMin( )). 2
Υλοποίηση Ουρών Προτεραιότητας MakeEmpty( ), IsEmpty( ), Insert( ), FindMin( ), DeleteMin( ) 1. µε χρήση πίνακα? Πολυπλοκότητες Χρόνου? 2. µε χρήση συνδεδεµένης λίστας? Πολυπλοκότητες Χρόνου? 3. µε χρήση ισοζυγισµένου δένδρου? Πολυπλοκότητες Χρόνου? Υπάρχουν άλλες λειτουργίες που υποστηρίζονται αποδοτικά, π.χ., LookUp, Delete, FindMax, DeleteMax? Μια ουρά προτεραιότητας που υποστηρίζει και τις λειτουργίες FindMax() και DeleteMax() ονοµάζεται διπλή ουρά προτεραιότητας (ή ουρά προτεραιότητας µε 2 άκρα). 3
Σωρός (ελαχίστων) Σωροί (Heaps) ένα πλήρες δυαδικό δένδρο, µε την εξής ιδιότητα: η προτεραιότητα (δηλαδή, το κλειδί) κάθε κόµβου είναι µικρότερη ή ίση εκείνης των παιδιών του κόµβου. Σηµ.: Ο σωρός είναι πλήρες δένδρο, ώστε το ύψος του να είναι O(log n) χωρίς να ισοζυγίζουµε. Που βρίσκεται ο κόµβος µε τη µικρότερη προτεραιότητα σε έναν σωρό? Σε κάθε µονοπάτι από τη ρίζα προς οποιονδήποτε κόµβο, η προτεραιότητα κάθε κόµβου είναι ίση ή µεγαλύτερη από την προτεραιότητα του προηγούµενου κόµβου στο µονοπάτι. 4
Εκτέλεση Λειτουργιών Ουρών Προτεραιότητας FindMin( ) επιστρέφει το στοιχείο <Κ,Ι> που βρίσκεται στη ρίζα του σωρού DeleteMin( ) Ο σωρός που προκύπτει µετά τη διαγραφή είναι ένα πλήρες δένδρο µε έναν κόµβο λιγότερο. Συγκεκριµένα, δεν έχει το δεξιότερο από τα φύλλα µεγίστου βάθους. Έτσι, διαγράφουµε αυτό το φύλλο, αφού πρώτα αντιγράψουµε τα δεδοµένα του στη ρίζα. 13 x 5
Εκτέλεση DeleteMin( ) (συνέχ.) Πρόβληµα: Το προκύπτον δένδρο ενδέχεται να µην πληροί την ιδιότητα του σωρού. Λύση: Ανταλλάσσουµε τα δεδοµένα της ρίζας µε τα δεδοµένα του παιδιού της µε τη µικρότερη προτεραιότητα εφόσον αυτή είναι µικρότερη από την προτεραιότητα της ρίζας, και επαναλαµβάνουµε την ίδια διαδικασία για το παιδί και ούτω καθεξής, µέχρι να φθάσουµε σε κόµβο µε προτεραιότητα µικρότερη από τις προτεραιότητες των παιδιών του, ή σε κόµβο φύλλο. 6
Εκτέλεση Insert( ) Μετά την εισαγωγή, θα πρέπει να έχουµε ένα πλήρες δένδρο µε έναν επιπλέον κόµβο. Έτσι, εισάγουµε το νέο στοιχείο ως αµέσως δεξιότερο φύλλο µεγίστου βάθους. Πρόβληµα: Το προκύπτον δένδρο ενδέχεται να µην πληροί την ιδιότητα του σωρού. Λύση: Ανταλλάσσουµε τα δεδοµένα του φύλλου µε εκείνα του πατέρα του εφόσον ο πατέρας έχει µεγαλύτερη προτεραιότητα και επαναλαµβάνουµε µέχρι το νέο στοιχείο να φθάσει σε κόµβο ο πατέρας του οποίου έχει µικρότερη προτεραιότητα, ή να φθάσει στη ρίζα. 7
Υλοποίηση Ο σωρός είναι εξαιρετικά αποδοτική δοµή για την υλοποίηση ουρών προτεραιότητας. Ο σωρός υλοποιείται όπως ένα πλήρες δυαδικό δένδρο υλοποιηµένο µε στατικό τρόπο, δηλαδή, µε πίνακα. Τότε: µετακίνηση από κόµβο σε παιδιά του σε σταθερό χρόνο µετακίνηση από κόµβο σε πατέρα του σε σταθερό χρόνο ύψος σωρού n στοιχείων = O(log n) Άρα: Πολυπλοκότητα χρόνου Insert( ) = O(log n) Πολυπλοκότητα χρόνου DeleteMin( ) = O(log n) 8
Εισαγωγή σε Σωρό (1 η Σωστή Υλοποίηση) (Χρησιµοποιήστε την όταν η πληροφορία τύπου info είναι µεγάλου µεγέθους) #define N 1000 /* στατικός πίνακας */ typedef struct element { key_type key; info data; } Element, *Εlement_ptr; typedef struct heap { Element_ptr table[n]; /* πίνακας δεικτών */ int size; } Heap, *Heap_ptr; procedure HeapInsert(key_type k, info d, Heap_ptr h) /* Εισάγει το ζεύγος <k,d> στον σωρό µε δείκτη h */ H = h->table; n = h->size; if (n == N) then error; /* Ο σωρός έχει γεµίσει */ m = n; /* m: ακέραιος δείκτης που µετακινείται προς τη ρίζα */ while (m > 0 and k < H[ (m-1)/2 ]->key) do H[m] = H[ (m-1)/2 ]; /* αντιγραφή δεικτών */ m = (m-1)/2 ; new_elem = new_struct(element); new_elem. key = k; new_elem.data = d; H[m] = διεύθυνση του new_elem; h->size = n+1; /* αύξηση µεγέθους σωρού */ 9
Εισαγωγή σε Σωρό (2 η Σωστή Υλοποίηση) (Χρησιµοποιήστε την όταν η πληροφορία τύπου info είναι µικρού µεγέθους ή έχουµε µόνο κλειδί) #define N 1000 /* στατικός πίνακας */ typedef struct element { key_type key; info data; } Element; typedef struct heap { Element table[n]; /* πίνακας στοιχείων */ int size; } Heap, *Heap_ptr; procedure HeapInsert(key_type k, info d, Heap_ptr h) /* Εισάγει το ζεύγος <k,d> στον σωρό µε δείκτη h */ H = h->table; n = h->size; if (n == N) then error; /* Ο σωρός έχει γεµίσει */ m = n; /* m: ακέραιος δείκτης που µετακινείται προς τη ρίζα */ while (m > 0 and k < H[ (m-1)/2 ].key) do /* αντιγραφή εγγραφήµατος (record) */ H[m].key = H[ (m-1)/2 ].key; H[m].data = H[ (m-1)/2 ].data; m = (m-1)/2 ; H[m]. key = k; H[m].data = d; h->size = n+1; /* αύξηση µεγέθους σωρού */ 10
ιαγραφή Ελαχίστου από Σωρό function HeapDeleteMin(heap_ptr h): Element /* ιάγραψε και επίστρεψε ένα στοιχείο ελάχιστης προτεραιότητας από τον σωρό µε δείκτη h */ H = h->table; n = h->size; if (n == 0) then error; /* Ο σωρός είναι άδειος */ elem.key = H[0].key; /* Το στοιχείο που επιστρέφεται */ elem.data = H[0].data; h->size = n-1; /* Το νέο µέγεθος του σωρού */ if (n == 1) then return(elem); /* αποµένει κενός σωρός */ n = n-1; /* πλήθος στοιχείων σωρού µετά τη διαγραφή */ k = H[n]. key; /* τιµή προτεραιότητας στοιχείου προς µετακίνηση */ m = 0; /* m: ακέραιος δείκτης που µετακινείται προς τα κάτω από τη ρίζα */ while ( 2m+1 < n ) do p = 2m+1; if (2m+2 < n and H[2m+1].key > H[2m+2].key) then p = 2m+2; /* ελάχιστη προτεραιότητα µεταξύ παιδιών στο H[p].key */ if (k < H[p].key) then βγες από τον βρόχο while; H[m] = H[p]; /* αντιγραφή περιεχοµένων εγγραφηµάτων */ m = p; H[m].key = k; /* = H[n].key */ H[m].data = H[n].data; return(elem); 11
Ταχεία κατασκευή σωρού n στοιχείων 1ος τρόπος µε διαδοχικές εισαγωγές σε αρχικά κενό σωρό Πολυπλοκότητα χρόνου: O(n logn) 2ος τρόπος αυθαίρετη αρχική τοποθέτηση κλειδιών και διόρθωση από κάτω προς τα άνω procedure MakeHeap (key_type A[n], Heap_ptr h) h->size = n; H = h->table; /* αυθαίρετη αρχική τοποθέτηση κλειδιών */ for i = 0 to n-1 do H[i] = A[i]; /* διόρθωση από κάτω προς τα άνω */ for i = n/2-1 downto 0 do βρες τη σωστή θέση του στοιχείου H[i] στο υποδένδρο µε το H[i] στη ρίζα του, ώστε αυτό να είναι σωρός Πολυπλοκότητα χρόνου: T n = O(n) 1. n 2 h, h = ύψος σωρού n στοιχείων 2. Ελέγχονται το πολύ 2 i υποδενδρα ύψους h-i: T n 0 i h [ 2 i c(h-i) ] 0 i h [ 2 i (n / 2 h ) c (h-i) ] n c 0 i h [ (h-i) / 2 h-i ] n c 0 k h [ k / 2 k ] = O(n) 12
Εφαρµογές Ουρών Προτεραιότητας (Σωρού) Ταξινόµηση των στοιχείων x 1, x 2,, x n Πως µπορούµε να ταξινοµήσουµε τα n στοιχεία? Αλγόριθµος (HeapSort) S = MakeEmpty( ); for (j = 0; j < n; j++) Insert(x j, S); /* x j = j-οστό στοιχείο */ for (j = n; --j >= 0; ) Print(DeleteMin(S)); /* αύξουσα σειρά */ Ποια είναι η πολυπλοκότητα χρόνου του αλγορίθµου αυτού? 13