ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ Εξωτερική Αναζήτηση και Β-δέντρα Κεφάλαιο 16 Ε. Μαρκάκης Επίκουρος Καθηγητής
Περίληψη Ακολουθιακή πρόσβαση Β-δέντρα Υλοποίηση πίνακα συµβόλων µε Β-δέντρα Αναζήτηση Εισαγωγή Δοµές Δεδοµένων 16-2
Εισαγωγή Εσωτερική και εξωτερική αναζήτηση Εσωτερική: τα (περισσότερα) δεδοµένα βρίσκονται στη µνήµη Κυριαρχεί το κόστος των πράξεων Εξωτερική: τα (περισσότερα) δεδοµένα δεν βρίσκονται στη µνήµη αλλά σε εξωτερική συσκευή (π.χ. σκληρός δίσκος) ή σε κάποια βάση δεδοµένων Κυριαρχεί το κόστος µεταφοράς δεδοµένων από/προς µνήµη, λειτουργίες I/O Εφαρµογές εξωτερικής αναζήτησης Αναζήτηση σε αρχεία στο δίσκο Μεταφορά δεδοµένων από/προς το δίσκο Αναζήτηση στην εικονική µνήµη Μεταφορά δεδοµένων από/προς τη φυσική µνήµη Αναζήτηση σε βάσεις δεδοµένων Μια βάση δεδοµένων αποτελείται από πολλά αρχεία Οι ερωτήσεις στις βάσεις ανάγονται σε αναζητήσεις στα αρχεία Δοµές Δεδοµένων 16-3
Οι κανόνες του παιχνιδιού Μοντέλο κόστους Η συσκευή µνήµης αποτελείται από σελίδες (µπλοκ πληροφοριών) Σελίδες µνήµης: µεταφέρονται από/προς την κρυφή µνήµη (cache) Σελίδες (µπλοκ) δίσκου: µεταφέρονται από/προς την κύρια µνήµη Κάθε σελίδα περιέχει πολλά συνεχόµενα δεδοµένα Ο χρόνος ανάγνωσης/εγγραφής των σελίδων κυριαρχεί Ο χρόνος επεξεργασίας σελίδων είναι 100-1000 φορές µικρότερος Η πρώτη πρόσβαση σε µία σελίδα ονοµάζεται διερεύνηση (probing) Δεδοµένα και ευρετήρια Τα δεδοµένα είναι αποθηκευµένα σε ένα αρχείο Ο εντοπισµός των δεδοµένων γίνεται µέσω ενός ευρετηρίου Μπορούµε να έχουµε πολλά ευρετήρια ανά αρχείο (µε βάση διαφορετικό κλειδί) Κάθε κλειδί µπορεί να παρέχει άλλου είδους επεξεργασία Τα ευρετήρια περιέχουν µόνο κλειδιά και όχι δεδοµένα Διατηρούµε αναφορές στα πραγµατικά δεδοµένα αντι να κρατάµε αντίγραφα σε κάθε ευρετήριο Δοµές Δεδοµένων 16-4
Ακολουθιακή πρόσβαση Βασική (και µη εφαρµόσιµη) ιδέα: ταξινοµηµένος πίνακας Πίνακας µε κλειδιά και αναφορές σε δεδοµένα Δυαδική αναζήτηση για εντοπισµό κλειδιών 1η βελτίωση: ισορροπηµένο δέντρο Κατάτµηση του ευρετηρίου σε σελίδες Εσωτερικοί κόµβοι: αναφέρονται στο ευρετήριο (στα κλειδιά) Εξωτερικοί κόµβοι (φύλλα): αναφέρονται στα δεδοµένα 2η βελτίωση: χρήση Μ-αδικού δέντρου Το βασικό κόστος είναι η ανάγνωση σελίδων Το κόστος προσπέλασης 2 καταχωρίσεων ίδιο µε προσπέλαση Μ καταχωρίσεων Γεµίζουµε τη σελίδα µε Μ κλειδιά / αναφορές (δηλ. µε όσα χωράει) Φτάνουµε στη βάση µε log M N διερευνήσεις Αν Μ=1000, log M N < 5 για N = 1 τρισεκατοµµύριο! Αυτή είναι η ακολουθιακή πρόσβαση µε ευρετήριο (indexed sequential access) Δοµές Δεδοµένων 16-5
Ακολουθιακή πρόσβαση Παράδειγµα: αναπαράσταση µε 5-αδικό δέντρο Τριψήφια οκταδικά κλειδιά (9 bits) Κάθε σελίδα είναι ένας κόµβος Εσωτερικοί κόµβοι: δείχνουν σε κλειδιά (στο µικρότερο κλειδί της σελίδας) Π.χ. Πρώτος δείκτης της ρίζας δείχνει σε κλειδιά που είναι 001 και < 153 Δεύτερος δείκτης: κλειδία στο διάστηµα [153, 373) Εξωτερικοί κόµβοι: δείχνουν σε δεδοµένα Δοµές Δεδοµένων 16-6
Ακολουθιακή πρόσβαση Απόδοση ευρετηρίου Αποτελεί την προτιµώµενη µέθοδο για εφαρµογές όπου δεν γίνονται συχνές αλλαγές στη βάση δεδοµένων Αναζήτηση: σχεδόν σταθερό πλήθος διερευνήσεων ίσο µε Ο(log M N) Προσοχή: Το βιβλίο λέει «σταθερό» αλλά ΔΕΝ είναι Ο(1) Στην πράξη βέβαια το log M N είναι πολύ µικρό όταν το Μ >>2 Παράδειγµα: έστω 10 30 αντικείµενα και Μ=1000 (!!!) log 1000 10 30 = 10 αφού (10 3 ) 10 = 10 30 Εισαγωγή: αν δεν είµαστε προσεκτικοί κοστίζει πολύ Πιθανή ανακατασκευή ολόκληρου του ευρετηρίου µετά από 1 εισαγωγή (αν έχουν ήδη γεµίσει οι σελίδες) Σε παλιότερα συστήµατα χρησιµοποιούσαµε σελίδες υπερχείλισης Στα σύγχρονα συστήµατα χρησιµοποιούµε B-δέντρα Δοµές Δεδοµένων 16-7
Β-δέντρα: γενίκευση των δέντρων 2-3-4 Μ-αδικά δέντρα µε περιορισµούς στο πλήθος συνδέσµων κάθε κόµβου Επιλέγουµε M τέτοιο ώστε κάθε κόµβος να χωράει σε µία σελίδα Κάθε κόµβος πρέπει να έχει M/2 έως M συνδέσµους Η ρίζα (µόνο) επιτρέπεται να έχει 2 έως M συνδέσµους Οι null σύνδεσµοι πρέπει να ισαπέχουν από τη ρίζα Τα δέντρα 2-3-4 είναι B-δέντρα µε M=4 Προτάθηκαν από τους Bayer, McCreight 1970 4-5-6-7-8 δέντρο = Β-δέντρο µε Μ = 8 Δοµές Δεδοµένων 16-8
Υλοποίηση B-δέντρων Κάθε κόµβος είναι ένας πίνακας στοιχείων/συνδέσµων Αναζήτηση: ξεκινάµε από τη ρίζα, τελειώνουµε πάντα στο τελευταίο επίπεδο Δεν κρατάµε δεδοµένα σε εσωτερικούς κόµβους, µόνο κλειδιά Σε κάθε επίπεδο κάνουµε δυαδική αναζήτηση για το κλειδί Αν το κλειδί υπάρχει θα βρεθεί στο τελευταίο επίπεδο Εισαγωγή: ξεκινάµε όπως στην αναζήτηση Εισάγουµε το κλειδί στο τελευταίο επίπεδο Αν δεν χωράει, σπάµε τον κόµβο σε δύο ίσα µέρη µε Μ/2 στοιχεία (αναδροµικά) Ανοδική εισαγωγή Μπορούµε να υλοποιήσουµε και καθοδική εισαγωγή όπως στα 2-3-4 δέντρα Δοµές Δεδοµένων 16-9
Παράδειγµα εισαγωγής Οι εσωτερικοί κόµβοι δεν δείχνουν σε δεδοµένα αλλά σε αντίγραφα των κλειδιών Εισαγωγή του 773 διασπά τη ρίζα... Εισαγωγή του 275: διάσπαση Εισαγωγή του 737 προκαλεί διάσπαση Δοµές Δεδοµένων 16-10
Παράδειγµα εισαγωγής Η ρίζα επιτρέπεται να έχει µόνο 2 στοιχεία... Εισαγωγή του 526 αυξάνει το ύψος Δοµές Δεδοµένων 16-11
Πρακτική υλοποίηση B-δέντρων Οι εξωτερικές σελίδες περιέχουν τα ίδια τα κλειδιά Κάθε καταχώριση δείχνει σε ένα στοιχείο στο δίσκο Οι εσωτερικές σελίδες περιέχουν αντίγραφα των κλειδιών Κάθε καταχώριση k δείχνει σε µία άλλη σελίδα Η σελίδα περιέχει κλειδιά που ξεκινάνε από k Περιεχόµενα κόµβου Πίνακας καταχωρίσεων Πλήθος ενεργών καταχωρίσεων (M/2 ως Μ εκτός από τη ρίζα) Κάθε καταχώριση στον κόµβο περιέχει Κλειδί: χρησιµοποιείται σε όλους τους κόµβους Δείκτης προς κόµβο: µόνο για εσωτερικούς κόµβους Δείκτης προς στοιχείο: µόνο για εξωτερικούς κόµβους Θα µπορούσαµε να έχουµε και διαφορετικούς τύπους κόµβων Δοµές Δεδοµένων 16-12
Τάξη B-δέντρου: το HT δείχνει το επίπεδο της ρίζας class ST { private class entry { // κλάση καταχώρισης KEY key; ITEM item; Node next; //2 διαφορετικοί constructors ανάλογα µε τον τύπο του κόµβου entry(key v, ITEM x) { key = v; item = x; } entry(key v, Node u) { key = v; next = u; } } private class Node { int m; // πλήθος ενεργών καταχωρίσεων entry[] b; // πίνακας καταχωρίσεων Node(int k){b = new entry[m]; m = k; } } private Node head; private int HT; //επίπεδο ρίζας ST(int maxn) { HT = 0; head = new Node(0); } ITEM search(key key) void insert(item x)} Δοµές Δεδοµένων 16-13
Αναζήτηση σε B-δέντρο Στο επίπεδο 0 (εξωτερικοί κόµβοι) ψάχνουµε για το ίδιο το κλειδί Στα άλλα επίπεδα ψάχνουµε για τον κατάλληλο κόµβο private ITEM searchr(node h, KEY v, int ht) { if (ht == 0) for (int j = 0; j < h.m; j++) { entry e = h.b[j]; if (equals(v, e.key)) return e.item; } else for (int j = 0; j < h.m; j++) if ((j+1 == h.m) less(v, h.b[j+1].key)) return searchr(h.b[j].next, v, ht-1); //θα µπορούσαµε να κάνουµε και binary search return null; } ITEM search(key key) {return searchr(head, key, HT);} Δοµές Δεδοµένων 16-14
Εισαγωγή σε B-δέντρο Αρχικά εισάγουµε αναδροµικά το στοιχείο (θα εισαχθεί τελικά στο τελευταίο επίπεδο, πιθανό να χρειαστεί διάσπαση κόµβου στα 2) Αν επιστραφεί null δεν χρειάζεται να σπάσει η ρίζα Αλλιώς, φτιάχνουµε νέα ρίζα µε 2 στοιχεία Το πρώτο δείχνει στην παλιά ρίζα και έχει το πρώτο κλειδί της Το δεύτερο δείχνει στον νέο κόµβο και έχει το πρώτο κλειδί του Αυξάνουµε το ύψος του δέντρου void insert(item x) { Node u = insertr(head, x, HT); if (u == null) return; //Ο u είτε θα είναι null Node t = new Node(2); //είτε το 2 ο παιδί της νέας ρίζας t.b[0] = new entry((head.b[0]).key, head); t.b[1] = new entry((u.b[0]).key, u); head = t; HT++; } Δοµές Δεδοµένων 16-15
Αναδροµική εισαγωγή σε B-δέντρο: 1ο µέρος Στο τέλος το j δείχνει στην επόµενη θέση από αυτή που γίνεται η εισαγωγή Αν δεν χρειάζεται αλλαγή στον κόµβο επιστρέφουµε private Node insertr(node h, ITEM x, int ht) { int i, j; KEY v = x.key(); Node u; entry t = new entry(v, x); if (ht == 0) for (j = 0; j < h.m; j++) {//εξωτερικός if (less(v, (h.b[j]).key)) break; } //ύψος 0: σταµατάµε εκεί που βρήκαµε µεγαλύτερο στοιχείο else for (j = 0; j < h.m; j++) if ((j+1 == h.m) less(v, (h.b[j+1]).key)) { u = insertr(h.b[j++].next, x, ht-1); if (u == null) return null; t.key = (u.b[0]).key; t.next = u; break; } Δοµές Δεδοµένων 16-16
Αναδροµική εισαγωγή σε B-δέντρο: 2ο µέρος Επιτρέπουµε να εισάγονται µέχρι Μ στοιχεία (1 πρόσθετη θέση) Αν έχουµε M στοιχεία χρειάζεται διάσπαση for (i = h.m; i > j; i--) h.b[i] = h.b[i-1]; //πρώτα γίνεται µετακίνηση των µεγαλύτερων κλειδιών κατά µία θέση όπως και στη βελτιωµένη εκδοχή της Insertionsort h.b[j] = t; h.m++; //εισαγωγή στη σωστή θέση που δείχνει ο j if (h.m < M) return null; else return split(h); } Διάσπαση κόµβου σε B-δέντρο Αντιγράφουµε τα M/2 µεγαλύτερα κλειδιά στον νέο κόµβο private Node split(node h) { Node t = new Node(M/2); h.m = M/2; for (int j = 0; j < M/2; j++) t.b[j] = h.b[m/2+j]; return t; } Δοµές Δεδοµένων 16-17
Απόδοση B-δέντρων Υποθέτουµε ότι οι κόµβοι χωράνε M κλειδιά Στην παραπάνω υλοποίηση σπάνε πάντα όταν έχουν M κλειδιά Η απόκλιση είναι ελάχιστη για πρακτικές τιµές του M Η αναζήτηση/εισαγωγή απαιτεί log M N ως log M/2 N διερευνήσεις Στην πράξη είναι ένας µικρός ουσιαστικά σταθερός αριθµός Η εισαγωγή µπορεί να οδηγήσει στο ίδιο πλήθος διασπάσεων Ο απαιτούµενος χώρος είναι 1,44N/M σελίδες κατά µέσο όρο Αφαίρεση στοιχείων Τι γίνεται όταν ένας κόµβος έχει λιγότερα από M/2 στοιχεία; Απαιτείται ανακατανοµή στοιχείων µε τα αδέλφια Μεταφορά ορισµένων δεικτών από τον διπλανό κόµβο Αναδροµική τροποποιήση των δεικτών του πατέρα Δοµές Δεδοµένων 16-18