ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ µε JAVA

Σχετικά έγγραφα
Oι βασικές πράξεις (λειτουργίες) που ορίζονται για τον τύπο στοίβα αναφέρονται παρακάτω:

2 ΓΡΑΜΜΙΚΕΣ ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ

1 ΕΙΣΑΓΩΓΗ. Πρωταρχικοί Τύποι

4 ΔYNAMIKEΣ ΔOMEΣ ΔEΔOMENΩN

Δομές Δεδομένων & Ανάλυση Αλγορίθμων. 3ο Εξάμηνο. Ουρά (Queue) Υλοποίηση της με τη βοήθεια πίνακα.

Δηµοσθένης Σταµάτης Τµήµα Πληροφορικής T.E.I. ΘΕΣΣΑΛΟΝΙΚΗΣ

Συνδεδεμένη Λίστα (Linked List)

ΠΑΡΑΡΤΗΜΑ: QUIZ ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ

ΑΤΕΙ ΘΕΣΣΑΛΟΝΙΚΗΣ. Δηµοσθένης Σταµάτης Τµήµα Πληροφορικής

Περιεχόµενα. 1 Εισαγωγή στις οµές εδοµένων 3. 2 Στοίβα (Stack) 5

Δηµοσθένης Σταµάτης Τµήµα Πληροφορικής T.E.I. ΘΕΣΣΑΛΟΝΙΚΗΣ

Συνδεδεμένη Λίστα ΣΛ null VK 23

Τύποι Δεδομένων και Απλές Δομές Δεδομένων. Παύλος Εφραιμίδης V1.0 ( )

Διάλεξη 05: Αφηρημένοι Τύποι Δεδομένων

ΕΠΛ231 Δομές Δεδομένων και Αλγόριθμοι 5. Αφηρημένοι Τύποι Δεδομένων / Στοίβες και Ουρές

Συλλογές, Στοίβες και Ουρές

Ευφυείς Τεχνολογίες ----Πράκτορες

Απλές Δοµές Δεδοµένων Στην ενότητα αυτή θα γνωρίσουµε ορισµένες απλές Δοµές Δεδοµένων και θα τις χρησιµοποιήσουµε για την αποδοτική επίλυση του προβλή

Βασικές οµές εδοµένων

Οι βασικές λειτουργίες (ή πράξεις) που γίνονται σε μια δομή δεδομένων είναι:

Βασικές Έννοιες Δοµών Δεδοµένων

ΠΛΗ111. Ανοιξη Μάθηµα 3 ο. Συνδεδεµένες Λίστες. Τµήµα Ηλεκτρονικών Μηχανικών και Μηχανικών Υπολογιστών Πολυτεχνείο Κρήτης

Βασικά Στοιχεία της Java

υναµικές οµές εδοµένων

Δομές Δεδομένων & Αλγόριθμοι

Αντικειµενοστρεφής Προγραµµατισµός

Δομές Δεδομένων (Data Structures)

public class ArrayStack implements Stack {

Κλάσεις στη Java. Στοίβα - Stack. Δήλωση της κλάσης. ΗκλάσηVector της Java. Ηκλάση Stack

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Κλάσεις και Αντικείμενα Αναφορές

Κλάσεις στη Java. Παύλος Εφραιμίδης. Java Κλάσεις στη Java 1

Βασικά Στοιχεία της Java

Αντικειμενοστρεφής Προγραμματισμός

2.1. Εντολές Σχόλια Τύποι Δεδομένων

Διάλεξη 06: Συνδεδεμένες Λίστες & Εφαρμογές Στοιβών και Ουρών

Αντικείµενα. ηµιουργία και χρησιµοποίηση αντικειµένων. ηµιουργία αντικειµένων

Διάλεξη 07: Λίστες Ι Υλοποίηση & Εφαρμογές

int array[10]; double arr[5]; char pin[20]; Προγραµµατισµός Ι

Πανεπιστήμιο Πειραιώς Σχολή Τεχνολογιών Πληροφορικής και Επικοινωνιών Τμήμα Ψηφιακών Συστημάτων ομές εδομένων

Πανεπιστήμιο Πειραιώς Σχολή Τεχνολογιών Πληροφορικής και Επικοινωνιών Τμήμα Ψηφιακών Συστημάτων ομές εδομένων

Βασικές Δομές Δεδομένων

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αντικείμενα με πίνακες. Constructors. Υλοποίηση Στοίβας

Διδάσκων: Κωνσταντίνος Κώστα Διαφάνειες: Δημήτρης Ζεϊναλιπούρ

Mεταβλητές (variables) και Σταθερές (constants)

ΕΛΛΗΝΙΚΗ ΔΗΜΟΚΡΑΤΙΑ ΠΑΝΕΠΙΣΤΗΜΙΟ ΚΡΗΤΗΣ. Δομές Δεδομένων. Ιωάννης Γ. Τόλλης Τμήμα Επιστήμης Υπολογιστών Πανεπιστήμιο Κρήτης

Κλάσεις και Αντικείµενα

Εισαγωγή στη JAVA. Εισαγωγή στη Java. Η Java είναι δημιούργημα της SUN MICROSYSTEMS.

ΚΕΦΑΛΑΙΟ 3 ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ ΚΑΙ ΑΛΓΟΡΙΘΜΟΙ

Ανάπτυξη εφαρμογών σε προγραμματιστικό περιβάλλον υποδειγματική διδασκαλία Κεφ. 3 Δομές Δεδομένων & αλγόριθμοι

Αντικείμενα στη Java. Παύλος Εφραιμίδης. Java Αντικείμενα στη Java 1

Βασικές δοµές δεδοµένων. Ορολογία λιστών. 8.1 Βασικές έννοιες δοµών δεδοµένων 8.2 Υλοποίηση δοµών δεδοµένων 8.3 Μια σύντοµη υπόθεση εργασίας

I (JAVA) Ονοματεπώνυμο: Α. Μ.: Δώστε τις απαντήσεις σας ΕΔΩ: Απαντήσεις στις σελίδες των ερωτήσεων ΔΕΝ θα ληφθούν υπ όψην.

Στοίβες - Ουρές. Στοίβα (stack) Γιάννης Θεοδωρίδης, Νίκος Πελέκης, Άγγελος Πικράκης Τµήµα Πληροφορικής

I (JAVA) Ονοματεπώνυμο: Α. Μ.: Δώστε τις απαντήσεις σας ΕΔΩ: Απαντήσεις στις σελίδες των ερωτήσεων ΔΕΝ θα ληφθούν υπ όψην.

Διάλεξη 3: Προγραμματισμός σε JAVA I. Διδάσκων: Παναγιώτης Ανδρέου

ΠΛΗΡΟΦΟΡΙΚΗ Ι JAVA Τμήμα θεωρίας με Α.Μ. σε 3, 7, 8 & 9 25/10/07

Αντικείμενα (Objects) στην Java. Αντικείμενα στη Java. Δημιουργία Αντικειμένων. Δηλώσεις Μεταβλητών (2) Ο τελεστής new (1)

Επιµέλεια Θοδωρής Πιερράτος

Οντοκεντρικός Προγραμματισμός

4. Συνδεδεμένες Λίστες

Διάλεξη 08: Λίστες ΙΙ Κυκλικές Λίστες

Wrapper Classes, Abstract Classes and Interfaces

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Κλάσεις και Αντικείμενα Constructors, equals, tostring

Δοµές Δεδοµένων. 6η Διάλεξη Αναδροµικές Εξισώσεις και Αφηρηµένοι Τύποι Δεδοµένων. Ε. Μαρκάκης

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Κλάσεις και Αντικείμενα

Εισαγωγή στον Προγραμματισμό

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αναφορές Στοίβα και Σωρός Αναφορές-Παράμετροι

ΕΛΛΗΝΙΚΗ ΔΗΜΟΚΡΑΤΙΑ ΠΑΝΕΠΙΣΤΗΜΙΟ ΚΡΗΤΗΣ. Δομές Δεδομένων. Ιωάννης Γ. Τόλλης Τμήμα Επιστήμης Υπολογιστών Πανεπιστήμιο Κρήτης

Κεφάλαιο 5 Συλλογές, Στοίβες και Ουρές

Αντικειμενοστρεφής Προγραμματισμός Διάλεξη 2 : ΜΕΤΑΒΛΗΤΕΣ ΤΕΛΕΣΤΕΣ & ΕΚΦΡΑΣΕΙΣ ΕΛΕΓΧΟΣ ΡΟΗΣ

ΑΝΑΠΤΥΞΗ ΕΦΑΡΜΟΓΩΝ ΣΕ ΠΡΟΓΡΑΜΜΑΤΙΣΤΙΚΟ ΠΕΡΙΒΑΛΛΟΝ

public class ArrayQueue implements Queue {

Δοµές Δεδοµένων. 7η Διάλεξη Αφηρηµένοι Τύποι Δεδοµένων. Ε. Μαρκάκης

3. Εκφράσεις και έλεγχος ροής

ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ. Στοιχειώδεις Δοµές Δεδοµένων Δοµικά Στοιχεία και Πίνακες Κεφάλαιο 3 (3.1 και 3.2) Ε. Μαρκάκης Επικ. Καθηγητής

ιαφάνειες παρουσίασης #11

ΠΛΗΡΟΦΟΡΙΚΗ ΙI Ενότητα 11: Vectors (διανύσματα)

Οντοκεντρικός Προγραμματισμός

ΠΛΗ111. Ανοιξη Μάθηµα 5 ο. Ουρά. Τµήµα Ηλεκτρονικών Μηχανικών και Μηχανικών Υπολογιστών Πολυτεχνείο Κρήτης

Στοιχειώδεις Δομές Δεδομένων

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Υπάρχουσες κλάσεις και αντικείμενα στην Java Strings Wrapper Classes Δομές

Εργαστήριο Java. Διδάσκουσα: Εργαστηριακοί Συνεργάτες:

Week. 6: Java Collections

2.1 Αντικειµενοστρεφής προγραµµατισµός

ΑΤΕΙ ΘΕΣΣΑΛΟΝΙΚΗΣ ΤΜΗΜΑ ΜΗΧΑΝΙΚΩΝ ΠΛΗΡΟΦΟΡΙΚΗΣ Αλγοριθμική και Προγραμματισμός

ιαφάνειες παρουσίασης #5 (β)

Πρόγραµµα 9.1 Πέρασµα δεδοµένων στην µνήµη

ΕΙΣΑΓΩΓΗ ΣΤΗΝ ΠΛΗΡΟΦΟΡΙΚΗ

Δομές Δεδομένων. Δημήτρης Μιχαήλ. Υλοποίηση Δυαδικού Σωρού σε γλώσσα Java. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αντικείμενα με πίνακες. Constructors. Υλοποίηση Στοίβας

Διαδικασιακός Προγραμματισμός

Προγραμματισμός Ι (ΗΥ120)

Σύνοψη Προηγούμενου (1/2) Στοίβες, Ουρές, Ουρές Προτεραιότητας. Σύνοψη Προηγούμενου (2/2) Σημερινό Μάθημα. Πίνακες. Εισαγωγή, σε χρόνο O(1).

Αντικειμενοστρεφής Προγραμματισμός Διάλεξη 6 : ΠΙΝΑΚΕΣ

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αναφορές Στοίβα και Σωρός Μνήμης Αντικείμενα ως ορίσματα

3.1 Αριθμητικοί και Λογικοί Τελεστές, Μετατροπές Τύπου (Casting)

ΠΛΗΡΟΦΟΡΙΑΚΑ & ΤΗΛΕΠΙΚΟΙΝΩΝΙΑΚΑ ΣΥΣΤΗΜΑΤΑ

Διάλεξη 08: Λίστες ΙΙ Κυκλικές Λίστες

Δομές Δεδομένων - Εργαστήριο 5. Ουρές Προτεραιότητας

Δεδομένα, τελεστές, είσοδος/έξοδος

Transcript:

Tµήµα ΠΛΗΡΟΦΟΡΙΚΗΣ Σχολή Τεχνολογικών Εφαρµογών A-Τ.Ε.Ι. Θεσσαλονίκης Σηµειώσεις Διαλέξεων ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ µε JAVA ΗΜΟΣΘΕΝΗΣ Ι. ΣΤΑΜΑΤΗΣ Θεσσαλονίκη 2006

Εισαγωγή 1 1 ΕΙΣΑΓΩΓΗ Η γλώσσα προγραµµατισµού Java είναι ισχυρά τυποποιηµένη (strongly typed), που σηµαίνει ότι κάθε µεταβλητή και κάθε έκφραση έχει κάποιο τύπο, ο οποίος πρέπει να είναι γνωστός κατά το χρόνο της µεταγλώττισης. Οι τύποι στη Java χωρίζονται σε δύο κατηγορίες: τους πρωταρχικούς τύπους (primitive types) και τους τύπους αναφοράς (reference types) Πρωταρχικοί Τύποι Οι πρωταρχικοί τύποι ονοµάζονται και βασικοί και χωρίζονται στους αριθµητικούς τύπους (numeric types) και στον λογικό τύπο boolean. Οι αριθµητικοί τύποι χωρίζονται µε τη σειρά τους στους τύπους περιοχής ή διαστήµατος (integral types) και στους τύπους κινητής υποδιαστολής (floating-point types). Η ιεραρχία των βασικών τύπων φαίνεται παρακάτω: Πρωταρχικοί τύποι Αριθµητικοί τύποι Τύποι διαστήµατος byte short int long char Τύποι υποδιαστολής float double Λογικός τύπος boolean

Εισαγωγή 2 Όλοι οι βασικοί τύποι δεδοµένων παρέχονται αυτόµατα από τον µεταγλωττιστή της Java και από πλευράς µεγέθους και εύρους τιµών είναι ίδιοι σε όλες της υλοποιήσεις της. Στον πίνακα που ακολουθεί φαίνονται το µέγεθος (σε bits) και το εύρος των τιµών του κάθε βασικού τύπου της Java. Τύπος Mέγεθος Ελάχιστη τιµή Μέγιστη τιµή wrapper τύπος char 16 bits Unicode 0 Unicode 2 16-1 Character byte 8 bits -128 +127 Byte short 16 bits -2 15 +2 15-1 Short int 32 bits -2 31 +2 31-1 Integer long 64 bits -2 63 +2 63-1 Long float 32 bits 1.402E-45 (*) 3.402E+38 (*) Float double 64 bits 4.94E-324 (*) 1.79E+308 (*) Double boolean void Boolean Void (*) τυποποίηση IEEE 754 Τύποι Αναφοράς Οι τύποι αναφοράς που ορίζονται στη Java ανήκουν σε τρεις κατηγορίες: τους τύπους κλάσης (class types) τους τύπους πίνακα (array types) και τους τύπους διασύνδεσης (interface types) Η κλάση αποτελεί το βασικό µηχανισµό παραγωγής νέων τύπων δεδοµένων από το χρήστη. Ο τύπος πίνακα ορίζεται µε τη βοήθεια ενός ειδικού µηχανισµού (βλέπε επόµενο κεφάλαιο) που ορίζεται από τη γλώσσα. Ο τύπος διασύνδεσης αποτελεί ένα µηχανισµό ορισµού αφηρηµένων τύπων δεδοµένων (abstract data types). Παραδείγµατα τύπων διασύνδεσης δίνονται στο κεφάλαιο 3. Στα πλαίσια της κλάσης που ορίζει έναν τύπο περιλαµβάνονται:

Εισαγωγή 3 (α) ένα σύνολο δηλώσεων τύπων (βασικών ή/και αναφοράς). Οι τύποι αυτοί ονοµάζονται µέλη δεδοµένων (data members) της κλάσης και ορίζουν την εσωτερική αναπαράσταση του τύπου. Οι δηλώσεις αυτές πρέπει να είναι ιδιωτικές (private) γιατί η αναπαράσταση του τύπου δεν πρέπει να γίνεται γνωστή εκτός της κλάσης. (β) ένα σύνολο από µεθόδους οι οποίες ορίζουν το σύνολο των πράξεων ή λειτουργιών του τύπου. Στις µεθόδους αυτές συµπεριλαµβάνονται και οι αντίστοιχοι δοµητές (constructors) της κλάσης. Οι µέθοδοι που αντιστοιχούν στις πράξεις του τύπου πρέπει να είναι δηµόσιες (public) γιατί αποτελούν την διασύνδεση χρήσης του τύπου. Στο τµήµα κώδικα 1 ορίζεται ο τύπος δεδοµένων παραλληλόγραµµο. Τα µέλη δεδοµένων του τύπου στην περίπτωση αυτή είναι δύο ακέραιοι height και width που ορίζουν την εσωτερική αναπαράσταση του παραλληλόγραµµου. Οι πράξεις που ορίζονται για το παραλληλόγραµµο είναι τέσσερις: υπολογισµός του ύψους (µέθοδος getheigth) υπολογισµός του πλάτους (µέθοδος getwidth) υπολογισµός εµβαδού (µέθοδος calculatearea) έλεγχος εάν πρόκειται για µεγάλο παραλληλόγραµµο (µέθοδος isbig) Η κλάση Rectangle λειτουργεί σαν µία γεννήτρια αντικειµένων που αποτελούν στιγµιότυπα του τύπου. Για τη δηµιουργία των στιγµιότυπων αυτών φροντίζουν οι δύο δοµητές της κλάσης. Ο ένας από αυτούς αναφέρεται στη δηµιουργία ενός τυχαίου παραλληλόγραµµου και ο δεύτερος στην περίπτωση του τετραγώνου. Αξίζει να παρατηρήσουµε ότι το ύψος και το πλάτος κάθε παραλληλόγραµµου δεν γίνεται γνωστό κατ ευθείαν µέσω των µεταβλητών heigth και width του κάθε στιγµιότυπου, αλλά µόνο µέσω της επίκλησης των αντίστοιχων µεθόδων getheigth και getwidth που αντιστοιχούν στις πράξεις του τύπου και πρέπει να σταλούν σαν µηνύµατα προς τα αντικείµενα. Η δηµιουργία 4 διαφορετικών αντικειµένων τύπου Rectangle καθώς και η χρήση των αντίστοιχων πράξεων του τύπου φαίνονται στο τµήµα κώδικα 2

Εισαγωγή 4 public class Rectangle // Ορισµός του τύπου Rectangle private int height, width; public Rectangle (int h, int w) height = h; width = w; public Rectangle (int s) height = width = s; public int getheigth( ) return heigth; public int getwidth( ) return width; public int calculatearea ( ) return height * width; private int perimeter ( ) return height * 2 + width * 2; public boolean isbig ( ) return perimeter( ) > 100; Τµήµα Κώδικα 1

Εισαγωγή 5 public static void main(string[ ] args) throws IOException int x,y; Rectangle square1 = new Rectangle (4); Rectangle square2 = new Rectangle (8); x = 9; y = 9; Rectangle box1 = new Rectangle (x,y); Rectangle box2 = new Rectangle (3,5); System.out.println("AreaA="+square1.calculateArea( )); System.out.println("AreaB="+square2.calculateArea( )); System.out.println("AreaC="+box1.calculateArea( )); System.out.println("AreaD="+box2.calculateArea( )); Τµήµα Κώδικα 2 Μετάπτωση Τύπου (Type Casting) Σε µερικές περιπτώσεις είναι αναγκαία η µετατροπή του τύπου µιας µεταβλητής σε κάποιον άλλο τύπο. Έτσι για παράδειγµα µπορεί η τιµή µιας µεταβλητής ακέραιου τύπου να χρειάζεται να καταχωρηθεί σε µία µεταβλητή πραγµατικού τύπου, ή να περάσει σαν παράµετρος σε µία µέθοδο η οποία είναι ορισµένη µόνο για πραγµατικές µεταβλητές. Η µετατροπή του τύπου µιας µεταβλητής σε έναν άλλο ονοµάζεται µετάπτωση (casting). Για να γίνει αυτό τοποθετούµε τον τύπο στον οποίο θέλουµε να µεταπέσει η µεταβλητή µέσα σε παρενθέσεις µπροστά από το όνοµα της µεταβλητής ή το όνοµα της µεθόδου. Για παράδειγµα η επόµενη πρόταση έχει σαν αποτέλεσµα την µετάπτωση της ακέραιας µεταβλητής που επιστρέφεται από τη µέθοδο calculatearea σε πραγµατική και την ανάθεση της τιµής της στην πραγµατική µεταβλητή f. float f = (float) calculatearea( ); Η διαδικασία της µετάπτωσης δεν είναι πάντοτε ασφαλής καθώς είναι πιθανόν να χαθεί µέρος της πληροφορίας ή να αλλοιωθεί η ακρίβεια µιας τιµής. Παραδείγµατος χάριν όταν έχουµε µετάπτωση µίας τιµής τύπου double σε µία τιµή τύπου float ο µεταγλωττιστής στρογγυλοποιεί αναγκαστικά την τιµή.

Εισαγωγή 6 Για να υπάρχει ασφάλεια κατά τη διαδικασία της µετάπτωσης τύπων θα πρέπει ο τύπος µετάπτωσης να είναι κατ ελάχιστον ίσος σε µέγεθος µε τον αρχικό τύπο. Ο πίνακας που ακολουθεί δείχνει ασφαλείς περιπτώσεις µετάπτωσης πρωταρχικών τύπων σε άλλους Αρχικός Τύπος byte short char int long float Τύπος Μετάπτωσης short, char, int, long, float, double int, long, float, double int, long, float, double long, float, double float, double double Είναι δυνατόν η διαδικασία της µετάπτωσης να ενεργοποιηθεί αυτόµατα από τον µεταγλωττιστή χωρίς να έχει προβλεφθεί από τον κώδικα του προγράµµατος. Στο επόµενο παράδειγµα η τιµή της µεταβλητής ch µεταπίπτει σε ακέραιο (τον αριθµό που κωδικοποιεί τον 'a') και στη συνέχεια υπολογίζεται η boolean τιµή της σύγκρισης στην if: int x = 10; char ch = 'a'; if (x > 'a')... Σε περιπτώσεις σαν τις παραπάνω µιλάµε για διαδικασία έµµεσης µετάπτωσης (implicit casting). Η έµµεση µετάπτωση κατά κανόνα θα πρέπει να αποφεύγεται σαν επικίνδυνη.

Εισαγωγή 7 Χώρος αποθήκευσης δεδοµένων στη Java. Η στοίβα (stack) Ο σωρός (heap) Στατική περιοχή µνήµης (static storage) Περιοχή σταθερών (constant storage) Καταχωρητές (registers) Δευτερεύουσα µνήµη

Γραµµικές οµές εδοµένων 1 2 ΓΡΑΜΜΙΚΕΣ ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ (Linear Data Structures) Ας θεωρήσουµε µία δοµή δεδοµένων το σύνολο των στοιχείων της οποίας είναι διατεταγµένο µε τέτοιο τρόπο ώστε να ισχύουν τα εξής: (α) υπάρχει ένα µόνο στοιχείο το οποίο ονοµάζεται αρχή και έχει ένα και µόνον ένα επόµενο στοιχείο (β) υπάρχει ένα µόνο στοιχείο το οποίο ονοµάζεται τέλος και έχει ένα και µόνον ένα προηγούµενο (γ) κάθε άλλο στοιχείο έχει ένα και µόνον ένα προηγούµενο και ένα και µόνον ένα επόµενο Στην περίπτωση αυτή λέµε ότι τα στοιχεία του συνόλου που αποτελούν τη δοµή δεδοµένων είναι γραµµικά διατεταγµένα και η δοµή ονοµάζεται γραµµική δοµή δεδοµένων (linear data structure). Στο κεφάλαιο αυτό εξετάζονται οι γραµµικές δοµές δεδοµένων πίνακας (array), το διάνυσµα (vector) και η συµβολοσειρά (string). Γραµµικές δοµές δεδοµένων είναι επίσης η στοίβα και η ουρά που εξετάζονται στο κεφάλαιο 3, καθώς και η συνδεδεµένη λίστα που παρουσιάζεται στο κεφάλαιο 4. 2.1 Πίνακες (Arrays) Οι πίνακες στη Java θεωρούνται αντικείµενα και εποµένως οι µεταβλητές τύπου πίνακα συµπεριφέρονται σαν µεταβλητές τύπου κλάσης, είναι δηλαδή αναφορές

Γραµµικές οµές εδοµένων 2 (references) σε αντικείµενα. Αν και δεν υπάρχει κλάση µε την ονοµασία Array αυτή υλοποιείται εσωτερικά από τη Java για λόγους αποτελεσµατικότητας. Είναι σηµαντικό να διαχωρίζουµε τη µεταβλητή πίνακα και το στιγµιότυπο του πίνακα στον οποίο αναφέρεται κατά το χρόνο εκτέλεσης του προγράµµατος. Δηλώνοντας ένα πίνακα όπως παρακάτω απλά δηλώνουµε µία µεταβλητή πίνακα τα στοχεία του οποίου πρέπει να είναι ακέραιοι αριθµοί: int[ ] pin; Ισοδύναµα η παραπάνω δήλωση χρησιµοποιώντας το συντακτικό της C/C++ µπορεί να γραφεί και: int pin[ ]; Για να δηµιουργήσουµε ένα στιγµιότυπο του πίνακα πρέπει να χρησιµοποιήσουµε τον τελεστή new καθορίζοντας σε συνδυασµό και µε τον τελεστή [ ] το πλήθος των στοιχείων του πίνακα, όπως για παράδειγµα: pin = new int[10]; Με την παραπάνω δήλωση γίνεται και ανάθεση µνήµης (στην περιοχή Heap) για τα στοιχεία του πίνακα. Το µέγεθος του πίνακα µπορεί να καθοριστεί από οποιαδήποτε έκφραση ακέραιου τύπου και δεν µπορεί να αλλάζει κατά τη διάρκεια της εκτέλεσης του προγράµµατος: int j = 5; int max = 10 *j; int [ ] pin = new int[max]; Ισοδύναµα δήλωση µεταβλητής και ανάθεση µνήµης για τα στοιχεία του πίνακα µπορεί να γίνει ταυτόχρονα: int[ ] pin = new int[10];

Γραµµικές οµές εδοµένων 3 Πολλές φορές είναι χρήσιµο να δώσουµε αρχικές τιµές στα στοιχεία ενός πίνακα, κάτι που αναφέρεται σαν αρχικοποίηση πίνακα (array initialization). Με την παρακάτω πρόταση: int[ ] pin = 2,4,6,8,10,12,14,16,18,20; γίνεται ταυτόχρονα δήλωση της µεταβλητής pin, ανάθεση µνήµης και καθορισµός του µεγέθους του πίνακα και τοποθετούνται αρχικές τιµές στα δέκα στοιχεία του πίνακα. Αξίζει να παρατηρήσουµε στην παραπάνω πρόταση την µε έµµεσο τρόπο - χωρίς τη χρήση του τελεστή new - δηµιουργία ενός στιγµιότυπου της κλάσης πίνακα. Κάτι τέτοιο επιτρέπεται αποκλειστικά λόγω της εσωτερικής υλοποίησης του πίνακα από τη Java. Πρέπει να ξεχωρίζουµε την έννοια της αρχικοποίησης των στοιχείων του πίνακα από αυτήν της αρχικοποίησης µεταβλητής πίνακα. Με την παρακάτω πρόταση: char[ ] data = null; δηλώνουµε µία µεταβλητή πίνακα χαρακτήρων data η οποία έχει αρχική τιµή null. Η δυνατότητα αρχικοποίησης µεταβλητής πίνακα πηγάζει από το γεγονός ότι αυτή είναι µία µεταβλητή αναφοράς. Τα στοιχεία ενός πίνακα µπορεί να είναι οποιοδήποτε τύπου, είτε βασικού τύπου είτε τύπου κλάσης. Στη δεύτερη περίπτωση αντιµετωπίζουµε κάθε στοιχείο του πίνακα σαν µία µεταβλητή τύπου κλάσης. Επίσης τα στοιχεία ενός πίνακα µπορεί να έχουν τύπο που ορίζεται από κλάση διασύνδεσης (interface). Στην περίπτωση αυτή τα στοιχεία µπορούν να έχουν σαν τιµή τους αναφορές σε στιγµιότυπα οποιασδήποτε κλάσης υλοποιεί τη διασύνδεση ή την τιµή null. Τέλος τα στοιχεία ενός πίνακα µπορεί να έχουν τύπο που ορίζεται από αφηρηµένη (abstract) κλάση. Στην περίπτωση αυτή τα στοιχεία µπορούν να έχουν σαν τιµή τους αναφορές σε στιγµιότυπα οποιασδήποτε υπο-κλάσης της αφηρηµένης κλάσης που όµως η ίδια δεν είναι αφηρηµένη.

Γραµµικές οµές εδοµένων 4 Στην περίπτωση που τα στοιχεία του πίνακα δεν αρχικοποιηθούν άµεσα µε τη χρήση δήλωσης αυτό γίνεται έµµεσα όπως φαίνεται στον πίνακα 2.1. Τύπος στοιχείων Αρχικοποίηση ακέραιος ή πραγµατικός 0 χαρακτήρας o χαρακτήρας null boolean false αντικείµενα τύπου κλάσης null Πίνακας 2.1 Για να αναφερθούµε σε ένα στοιχείο του πίνακα πρέπει να χρησιµοποιήσουµε το όνοµα της µεταβλητής του πίνακα ακολουθούµενο από τον τελεστή [ ] περιλαµβάνοντας ένα συγκεκριµένο ενδείκτη (subscript). Ο ενδείκτης του πίνακα πρέπει να είναι φυσικός αριθµός ή έκφραση: a = 3; b = 5; pin[a+b] += 2; H Java πραγµατοποιεί έλεγχο εύρους (range checking) στους ενδείκτες των πινάκων κατά τη διάρκεια της εκτέλεσης του προγράµµατος. Προσπάθεια να χρησιµοποιήσουµε ενδείκτη µε τιµή µικρότερη του µηδενός ή µεγαλύτερη ή ίση του µήκους του πίνακα οδηγεί στην κατάσταση εξαίρεσης IndexOutOfBoundExeption. Το µήκος του πίνακα (πλήθος των στοιχείων του) µπορεί να επιστραφεί µε τη χρήση του µέλους δεδοµένων (data member) length που αντιστοιχεί σε κάθε πίνακα: int i = pin.length Το µήκος του πίνακα δεν µπορεί να αλλάζει δυναµικά κατά τη διάρκεια εκτέλεσης του προγράµµατος καθώς η τιµή του length είναι τιµή µόνον ανάγνωσης: pin.length =100; // Προσοχή, λάθος!

Γραµµικές οµές εδοµένων 5 Τα στοιχεία ενός πίνακα µπορεί να είναι µε τη σειρά τους αλλοι πίνακες. Στην περίπτωση αυτή ο πίνακας ονοµάζεται πολυδιάστατος. Παραδείγµατα πολυδιάστατων πινάκων δίνονται παρακάτω: int[ ] [ ] pin2; pin2 = new int [3] [4]; int[ ] [ ] pin3= 4,5, 1,7 ; Τα στοιχεία ενός πολυδιάστατου πίνακα µπορεί να είναι πίνακες διαφορετικού µήκους όπως φαίνεται στο επόµενο παράδειγµα: int[ ] [ ] pin4; pin4 = new int[2] [ ]; pin4[0] = new int[3]; pin4[1] = new int[5]; όπου ο πίνακας pin4 αποτελείται από δύο γραµµές: ένα πίνακα µήκους 3 και ένα πίνακα µήκους 5. 2.2 Διανύσµατα (Vectors) Ενώ ένας πίνακας είναι στατικός ως προς το µέγεθός του, µε τη βοήθεια της κλάσης Vector µπορούµε να υλοποιήσουµε διανύσµατα - µία µορφή πινάκων οι οποίοι µπορούν να µεταβάλουν το µήκος τους κατά τη διάρκεια της εκτέλεσης του προγράµµατος. Ένα διάνυσµα περιέχει στοιχεία τα οποία µπορεί να είναι οποιοδήποτε τύπου και τα στοιχεία αυτά είναι προσβάσιµα όπως και στην περίπτωση του πίνακα µέσω ενός ακέραιου ενδείκτη. Η κλάση Vector ορίζεται στα πλαίσια του πακέτου java.util. Μπορούµε να δηµιουργήσουµε ένα διάνυσµα µε τρεις διαφορετικούς τρόπους: (α) Καθορίζοντας την αρχική του χωρητικότητα. Στο παράδειγµα που ακολουθεί το διάνυσµα v1 έχει αρχική χωρητικότητα µία θέση. Vector v1 = new Vector(1);

Γραµµικές οµές εδοµένων 6 Κάθε φορά που παρουσιάζεται ανάγκη χρήσης περισσότερων στοιχείων η χωρητικότητα του διανύσµατος διπλασιάζεται. (β) Να µην ορίσουµε ρητά την αρχική χωρητικότητα του διανύσµατος, όπως φαίνεται στο παρακάτω παράδειγµα: Vector v2 = new Vector( ); Στην περίπτωση αυτή η αρχική χωρητικότητα καθορίζεται από το σύστηµα της Java να είναι 10 θέσεις. Και στην περίπτωση αυτή κάθε φορά που παρουσιάζεται ανάγκη χρήσης περισσότερων στοιχείων η χωρητικότητα του διανύσµατος διπλασιάζεται. (γ) Να ορίσουµε την αρχική χωρητικότητα του διανύσµατος, καθώς και το βήµα αύξησης της χωρητικότητας, όπως φαίνεται στο παρακάτω παράδειγµα: Vector v3 = new Vector(10, 5); Στην περίπτωση αυτή η αρχική χωρητικότητα είναι 10 θέσεις. Αυξάνεται κατά 5 όταν υπάρχει ανάγκη Οι βασικές λειτουργίες που αναφέρονται στα διανύσµατα υλοποιούνται από τις µεθόδους που φαίνονται στον πίνακα 2.2. Μέθοδος void addelement(object Item ) Περιγραφή προσθέτει το Item στο τέλος του διανύσµατος αυξάνοντας το µέγεθός του κατά 1. Void insertelementat(object Item, int pos) Παρεµβάλει το Item σαν νέο στοιχείο του διανύσµατος στη θέση pos void setelementat(object Item, int pos) boolean removeelement(object Item) boolean isempty Τοποθετεί στο υπ αριθµό pos στοιχείο του διανύσµατος το Item ιαγράφει την πρώτη εµφάνιση του στοιχείου Item από το διάνυσµα Ελέγχει εάν το διάνυσµα δεν έχει καθόλου

Γραµµικές οµές εδοµένων 7 στοιχεία Object firstelement( ) Επιστρέφει το πρώτο στοιχείο του διανύσµατος (αυτό που βρίσκεται στη θέση 0) Μέθοδος Πίνακας 2.2 Περιγραφή Object lastelement( ) boolean contains(object Item) void trimtosize( ) int size( ) int capacity( ) Επιστρέφει το τελευταίο στοιχείο του διανύσµατος Ελέγχει εάν το Item αποτελεί στοιχείο του διανύσµατος Ψαλιδίζει τη χωρητικότητα του διανύσµατος ώστε να γίνει ίση µε το τρέχον µέγεθός του Επιστρέφει το τρέχον µέγεθος του διανύσµατος Επιστρέφει τη χωρητικότητα του διανύσµατος Πίνακας 2.2 (συνέχεια) 2.3 Συµβολοσειρές (Strings) Στη Java υπάρχουν δύο κλάσεις µε τη βοήθεια των οποίων µπορούµε να δηµιουργήσουµε και να χειριστούµε συµβολοσειρές. Η κλάση String και η κλάση StringBuffer οι οποίες ορίζονται στα πλαίσια του πακέτου java.lang. Τα στιγµιότυπα και της κλάσης String και της κλάσης StringBuffer αναπαριστούν ακολουθίες Unicode χαρακτήρων. Οι συµβολοσειρές που δηµιουργούνται στα πλαίσια της κλάσης String ονοµάζονται αµετάβλητες (immutable). Ένα αντικείµενο τύπου String από τη στιγµή που θα δηµιουργηθεί δεν µπορεί να αλλάξει τιµή. Αυτό σηµαίνει ότι οποιαδήποτε λειτουργία τροποποιεί µία συµβολοσειρά οδηγεί στη δηµιουργία ενός νέου αντικειµένου. Έστω ότι δύνονται δύο συµβολοσειράς S και Q. Στον πίνακα 2.3 περιγράφονται οι µέθοδοι που αντιστοιχούν στις βασικές πράξεις του τύπου String.

Γραµµικές οµές εδοµένων 8 Μέθοδος length( ) charat(i) Περιγραφή Επιστρέφει το µήκος της συµβολοσειράς S Επιστρέφει τον χαρακτήρα που βρίσκεται στη θέση i της συµβολοσειράς S Πίνακας 2.3 Μέθοδος concat(q) endswith(q) equals(q) indexof(q) startswith(q) substring(i,j) Περιγραφή Επιστρέφει τη συνένωση των συµβολοσειρών S και Q χωρίς να αλλάζει την τιµή της S Ελέγχει εάν το string S τελειώνει µε το string Q Ελέγχει εάν το string S είναι ίσο µε το string Q Εάν το string Q είναι υπό-string του S και επιστρέφει τον ενδείκτη της πρώτης εµφάνισης του στο S, αλλιώς επιστρέφει -1 Επιστρέφει το υπό-string του S που αρχίζει από τη θέση i και τελειώνει στη θέση j Πίνακας 2.3 (συνέχεια) Με το πρόγραµµα που φαίνεται στο τµήµα κώδικα 1 µπορεί να ελεγχθεί η λειτουργία των βασικών πράξεων του τύπου String. class CheckString public static void main(string arguments[ ]) String str = "Πολύ µου αρέσει η γλώσσα προγραµµατισµού Java"; System.out.println("H συµβολοσειρά είναι: " + str); System.out.println("Το µήκος της συµβολοσειράς είναι: " + str.length( )); System.out.println("Ο χαρακτήρας στη θέση 7: " + str.charat(7)); System.out.println("Το υπο-string από 24 µέχρι 31:" + str.substring(24,31)); System.out.println("Ο ενδείκτης του χαρακτήρα x: " + str.indexof('x')); System.out.println("Ο ενδείκτης της αρχής του " + "υπο-string \"γλώσσα\": " + str.indexof("γλώσσα")); System.out.println("Το string µε κεφαλαία: " + str.touppercase());

Γραµµικές οµές εδοµένων 9 Τµήµα κώδικα 1 Οι συµβολοσειρές που δηµιουργούνται στα πλαίσια της κλάσης StringBuffer ονοµάζονται ευµετάβλητες (mutable). Ένα αντικείµενο τύπου StringBuffer µπορεί να αλλάζει τιµή κατά τη διάρκεια της εκτέλεσης του προγράµµατος Δοθείσης µίας συµβολοσειράς S και µίας Q στον παρακάτω πίνακα (πίνακας 2.4) περιγράφονται οι µέθοδοι που αντιστοιχούν στις βασικές πράξεις του τύπου StringBuffer. Μέθοδος append(q) insert(i,q) reverse( ) setcharat(i,ch) Περιγραφή Επιστρέφει τη συνένωση των συµβολοσειρών S και Q επιστρέφοντας το αποτέλεσµα στη συµβολοσειρά S. Επιστρέφει τη συµβολοσειρά που προκύπτει από την εισαγωγή της Q στη συµβολοσειρά S ξεκινώντας από τη θέση i της S. Tο αποτέλεσµα τροποποιεί την S. Αντιστρέφει τη συµβολοσειρά S Τοποθετεί τον υπ αριθµό i χαρακτήρα της συµβολοσειράς S µε ch. Tο αποτέλεσµα τροποποιεί την S. Πίνακας 2.4 2.4 Η κλάση StringTokenizer Όταν επεξεργαζόµαστε µία µεγάλη συµβολοσειρά, όπως για παράδειγµα ένα κοµµάτι κειµένου τότε είναι λογικό να τη χωρίζουµε σε επί µέρους τµήµατα - λέξεις, τα οποία ανάλογα µε τη φύση του κειµένου έχουν κάποιο συγκεκριµένο νόηµα. Τα τµήµατα αυτά ονοµάζονται tokens. Tα tokens ξεχωρίζουν µεταξύ τους µε τη βοήθεια ειδικών οριοθετών. Οι οριοθέτες αυτοί µπορεί να είναι: ένας ή περισσότεροι κενοί χαρακτήρες ειδικοί χαρακτήρες, όπως tab και newline ειδικοί χαρακτήρες αλλαγής γραµµής, όπως newline και carriage return

Γραµµικές οµές εδοµένων 10 Στη Java στα πλαίσια του πακέτου java.util ορίζεται η κλάση StringTokenizer, η οποία είναι ιδιαίτερα χρήσιµη για τον χωρισµό µίας συµβολοσειράς σε tokens και τον χειρισµό των tokens αυτών. Στα τµήµατα κώδικα 2 και 3 φαίνονται δύο παραλλαγές ενός προγράµµατος, όπου χρησιµοποιείται η κλάση StringTokenizer. import java.io.*; import java.util.*; public class Setences public static void main(string[ ] args) throws IOException String s1; int i; BufferedReader br; br = new BufferedReader(new InputStreamReader(System.in)); s1 = br.readline( ); StringTokenizer tokens = new StringTokenizer(s1); System.out.println(tokens.countTokens( )); while (tokens.hasmoretokens( )) System.out.println(tokens.nextToken( )); Τµήµα Κώδικα 2

Γραµµικές οµές εδοµένων 11 import java.io.*; import java.util.*; public class Setences2 public static void main(string[ ] args) throws IOException String s1; int i,j; String ArrayOfTokens[ ] = new String[20]; BufferedReader mybuffer; mybuffer = new BufferedReader(new InputStreamReader(System.in)); s1 = mybuffer.readline(); StringTokenizer tokens = new StringTokenizer(s1); i = tokens.counttokens(); System.out.println(i); j = 0; while (tokens.hasmoretokens( ) && j < ArrayOfTokens.length) ArrayOfTokens[j] = tokens.nexttoken( ); j++; for (j=0; j<i; j++) System.out.println(ArrayOfTokens[j]); Τµήµα κώδικα 3

Στοίβες και Ουρές 1 3 ΣTOIBEΣ KAI OYPEΣ 3.1 ΣΤΟΙΒΕΣ Στοίβα (stack) είναι µία λίστα στην οποία νέα στοιχεία µπορούν να προστεθούν και να αφαιρεθούν µόνο από τη µία άκρη της (κορυφή της στοίβας). Συχνά µία στοίβα αναφέρεται και σαν µία λίστα τύπου LIFO (Last-In-First-Out), για να δηλώνεται έτσι ρητά η βασική της ιδιότητα, ότι το στοιχείο που θα προστεθεί τελευταίο στη στοιβάδα θα αφαιρεθεί πρώτο ή ισοδύναµα το πρώτο στοιχείο που θα προστεθεί στη στοιβάδα, αναγκαστικά πρέπει να αφαιρεθεί τελευταίο. Bασικές πράξεις σε στοίβες: Oι βασικές πράξεις (λειτουργίες) που ορίζονται για τον τύπο στοίβα αναφέρονται παρακάτω: 1) Δηµιουργία Στοίβας: Mε την πράξη αυτή δηµιουργείται µία κενή στοίβα S, η οποία δεν περιέχει κανένα στοιχείο. 2) Eισαγωγή στοιχείου σε Στοίβα - push(p): Δοθείσης µίας στοίβας S, το στοιχείο P τοποθετείται στην κορυφή της στοίβας S, σαν τελευταίο στοιχείο. 3) Eξαγωγή στοιχείου από Στοίβα - pop( ): Tο στοιχείο P που βρίσκεται στην κορυφή της στοίβας S αφαιρείται από αυτήν και επιστρέφεται.

Στοίβες και Ουρές 2 4) Eλεγχος κενής στοιβάδας - isempty( ): Δοθείσης µίας στοίβας S, επιστρέφει την τιµή true, εάν η στοίβα S δεν έχει κανένα στοιχείο, ενώ στην αντίθετη περίπτωση την τιµή false. Επιπλέον των πράξεων αυτών που θεωρούνται βασικές συνήθως ορίζονται και οι πράξεις: 5) Κορυφή της στοίβας - top( ): Tο στοιχείο P που βρίσκεται στην κορυφή της στοίβας S επιστρέφεται, χωρίς όµως να αφαιρεθεί από αυτήν. 6) Μέγεθος της στοίβας - size( ): Δοθείσης µίας στοίβας S επιστρέφει το πλήθος των στοιχείων της (ενεργό µήκος της στοίβας). Η παρακάτω διασύνδεση (interface) ορίζει τον αφηρηµένο τύπο δεδοµένων (abstract data type) στοίβα: public interface Stack public int size(); // επιστρέφει το µέγεθος της στοίβας public boolean isempty(); // αληθεύει εάν η στοίβα είναι κενή public Object top( ) throws StackEmptyException; // επιστρέφει το στοιχείο που βρίσκεται στην κορυφή της στοίβας public void push(object item) throws StackFullException; // εισάγει ένα νέο στοιχείο στην κορυφή της στοίβας public Object pop( ) throws StackEmptyException; // εξάγει και επιστρέφει το στοιχείο που βρίσκεται στην κορυφή της στοίβας Τµήµα Κώδικα 1

Στοίβες και Ουρές 3 3.1.1 Yλοποίηση Στοίβας µε τη Bοήθεια Πίνακα Mία στοίβα µπορεί να αναπαρασταθεί µε τη βοήθεια ενός πίνακα (συγκεκριµένου µήκους) και ενός ακεραίου "ενδείκτη" ο οποίος δείχνει το στοιχείο του πίνακα που αποτελεί την κορυφή της στοίβας. S 0 N-1 top O πίνακας αυτός µαζί µε τον ενδείκτη µπορούν να οµαδοποιηθούν µε τη βοήθεια δηλώσεων της κλάσης ArrayStack, που υλοποιεί τον αφηρηµένο τύπο Stack, όπως φαίνεται παρακάτω: public class ArrayStack implements Stack public static final int CAPACITY = 1000; private int capacity; private Object[ ] S; private int top = -1;. 1) Δηµιουργία Στοίβας: Η κενή στοίβα που δηµιουργείται µπορεί να παρασταθεί µε έναν άδειο πίνακα και την αρχική τιµή του ενδείκτη top = -1-1 0 N-1 S top

Στοίβες και Ουρές 4 Το µέγεθος της στοίβας µπορεί να καθορίζεται είτε έµµεσα π.χ 1000 στοιχεία είτε άµεσα κατά τη δηµιουργία της στοίβας. Οι δοµητές της κλάσης ArrayStack για τις δύο αυτές περιπτώσεις είναι οι εξής: public ArrayStack( ) this(capacity); public ArrayStack(int cap) capacity = cap; S = new Object[capacity]; 2) Eισαγωγή στοιχείου στη Στοίβα: Η διαδικασία εισαγωγής ενός στοιχείου στη στοίβα συνίσταται στην αύξηση του ενδείκτη top κατά ένα και την τοποθέτηση του στοιχείου στη θέση του πίνακα που δείχνει ο top. Στην περίπτωση που ο χώρος της στοίβας έχει εξαντληθεί από προηγούµενες εισαγωγές στοιχείων, δηµιουργείται κατάσταση εξαίρεσης (υπερχείλιση στοίβας). Η µέθοδος push της ArrayStack είναι η εξής: public void push(object item) if (size( )== capacity) throw new StackFullException("Stack overflow"); S[++top] = item; Η εξαίρεση StackFullException πρέπει να οριστεί µε τη βοήθεια ξεχωριστής κλάσης, η οποία επεκτείνει την κλάση RuntimeException. 3) Eξαγωγή στοιχείου από Στοίβα: Η διαδικασία εξαγωγής ενός στοιχείου από τη στοίβα συνίσταται στην επιστροφή του στοιχείου που βρίσκεται στην κορυφή της στοίβας και τη µείωση του ενδείκτη top κατά ένα. Εάν κατά την πράξη εξαγωγής στοιχείου η στοίβα βρεθεί άδεια δηµιουργείται κατάσταση εξαίρεσης (άδεια στοίβα).

Στοίβες και Ουρές 5 public Object pop( ) throws StackEmptyException Object element; if (isempty( )) throw new StackEmptyException("Stack is empty"); element = S[top]; S[top--] = null; //!!! Χρειάζεται για τον garbage collector return element; Η εξαίρεση StackEmptyException πρέπει επίσης να οριστεί µε τη βοήθεια ξεχωριστής κλάσης. 4) Eλεγχος κενής στοίβας: Είτε στην περίπτωση που η στοίβα έχει µόλις δηµιουργηθεί είτε στην περίπτωση που η στοίβα δεν έχει κανένα στοιχείο µετά από διαδοχικές εισαγωγές και εξαγωγές στοιχείων ο ενδείκτης top θα πρέπει να έχει την τιµή -1: public boolean isempty( ) return (top < 0); Στο τµήµα κώδικα 2 δίνεται ολόκληρη η υλοποίηση της κλάσης ArrayStack και στο τµήµα κώδικα 3 των κλάσεων που υλοποιούν τις εξαιρέσεις. public class ArrayStack implements Stack public static final int CAPACITY = 1000; private int capacity; private Object[ ] S; private int top = -1; public ArrayStack( ) this(capacity);

Στοίβες και Ουρές 6 public ArrayStack(int cap) capacity = cap; S = new Object[capacity]; public int size( ) return (top+1); public boolean isempty( ) return (top < 0); public void push(object item) if (size( )== capacity) throw new StackFullException("Stack overflow"); S[++top] = item; public Object top( ) throws StackEmptyException if (isempty( )) throw new StackEmptyException("Stack is empty"); return S[top]; public Object pop() throws StackEmptyException Object element; if (isempty()) throw new StackEmptyException("Stack is empty"); element = S[top]; S[top--] = null; //!!! για τον garbage collector return element; Τµήµα Κώδικα 2

Στοίβες και Ουρές 7 public class StackEmptyException extends RuntimeException public StackEmptyException(String err) super(err); public class StackFullException extends RuntimeException public StackFullException(String err) super(err); Τµήµα Κώδικα 3 Σαν ένα παράδειγµα χρήσης της στοίβας στο τµήµα κώδικα 4 δίνεται η κλάση ReverseArray η οποία υλοποιεί τη διαδικασία αντιστροφής των στοιχείων ενός πίνακα χρησιµοποιώντας τις βασικές πράξεις της στοίβας. import java.io.*; class ReverseArray public static String[ ] Reverse(String[ ] a) int i; ArrayStack s = new ArrayStack( ); for (i=0; i<a.length; ++i) s.push(a[i]); i=0; while (!s.isempty( )) a[i]=(string)s.pop(); i++; return a;

Στοίβες και Ουρές 8 public static void main(string[ ] args) throws IOException String b[ ] = "nikos","demos","maria","lina","thomas"; String c[ ] = new String[b.length]; ReverseArray RA = new ReverseArray(); c = RA.Reverse(b); for (int i=0; i<c.length; ++i) System.out.println(c[i]); Τµήµα Κώδικα 4 3.2 OYPEΣ Oυρά (queque) είναι µια λίστα στην οποία µπορούν να προστεθούν στοιχεία µόνο στη µία άκρη (πίσω) και να αφαιρεθούν µόνο από την άλλη (µπροστά). Mια ουρά αναφέρεται και σαν λίστα τύπου FIFO (First-In- First-Out). Bασικές πράξεις σε ουρές: Oι βασικές πράξεις (λειτουργίες) που ορίζονται για τον τύπο ουρά είναι οι εξής: 1) Δηµιουργία Oυράς: Mε την πράξη αυτή δηµιουργείται µία κενή ουρά Q, η οποία δεν περιέχει κανένα στοιχείο. 2) Eισαγωγή στοιχείου σε Oυρά - enqueue(p): Δοθείσης µίας ουράς Q, το στοιχείο P τοποθετείται στο πίσω µέρος της σαν τελευταίο στοιχείο. 3) Eξαγωγή στοιχείου από Oυρά - dequeue(p): Δοθείσης µίας ουράς Q, το στοιχείο P που βρίσκεται στην αρχή της αφαιρείται από αυτήν και επιστρέφεται. 4) Eλεγχος κενής Oυράς - isempty( ): Eπιστρέφει την τιµή true, εάν η ουρά Q είναι κενή, ενώ στην αντίθετη περίπτωση την τιµή false. Επιπλέον των πράξεων αυτών που θεωρούνται βασικές συνήθως ορίζεται και η πράξη:

Στοίβες και Ουρές 9 5) Μέγεθος της ουράς - size( ): Δοθείσης µίας ουράς Q επιστρέφει το πλήθος των στοιχείων της (ενεργό µήκος της ουράς). 6) Πρώτο στοιχείο της ουράς - front( ): Δοθείσης µίας ουράς Q επιστρέφει το πρώτο στοιχείο της ουράς, χωρίς όµως να το αφαιρεί από αυτήν. Η παρακάτω διασύνδεση (interface) ορίζει τον αφηρηµένο τύπο δεδοµένων (abstract data type) ουρά (τµήµα κώδικα 5). Στο τµήµα κώδικα 6 δίνονται οι κλάσεις που υλοποιούν τις εξαιρέσεις που σχετίζονται µε την ουρά: public interface Queue public int size( ); // επιστρέφει το µέγεθος (αριθµός στοιχείων) της ουράς public boolean isempty( ); // αληθεύει εάν η ουρά είναι κενή public Object front( ) throws QueueEmptyException; // επιστρέφει το στοιχείο που βρίσκεται στo εµπρός µέρος της ουράς public void enqueue(object item); // εισάγει ένα νέο στοιχείο στο πίσω µέρος της ουράς public Object dequeue( ) throws QueueEmptyException; // εξάγει και επιστρέφει το στοιχείο που βρίσκεται // στo εµπρός µέρος της ουράς Τµήµα κώδικα 5 public class QueueEmptyException extends RuntimeException public QueuekEmptyException(String err) super(err);

Στοίβες και Ουρές 10 public class QueueFullException extends RuntimeException public QueueFullException(String err) super(err); Τµήµα Κώδικα 6 3.2.1 Yλοποίηση Oυράς µε τη Bοήθεια Πίνακα Mια ουρά µπορεί να αναπαρασταθεί µε τη βοήθεια ενός πίνακα και δύο ακεραίων ενδεικτών από τους οποίους ο ένας δείχνει το στοιχείο του πίνακα που αποτελεί το τελευταίο στοιχείο της ουράς και ο άλλος το στοιχείο του πίνακα που αποτελεί το πρώτο στοιχείο της. Q 0 N-1 first last O πίνακας αυτός µαζί µε τους δύο ενδείκτες µπορούν να οµαδοποιηθούν µε τη βοήθεια δηλώσεων της κλάσης ArrayQueue, που υλοποιεί τον αφηρηµένο τύπο Queue, όπως φαίνεται παρακάτω: public class ArrayQueue implements Queue public static final int CAPACITY = 1000; private int capacity; private Object[ ] Q; private int first = 0; private int last = 0;..

Στοίβες και Ουρές 11 1) Δηµιουργία Oυράς: Το µέγεθος της ουράς µπορεί να καθορίζεται είτε έµµεσα π.χ 1000 στοιχεία είτε άµεσα κατά τη δηµιουργία της. Οι δοµητές της κλάσης ArrayQueue για τις δύο αυτές περιπτώσεις είναι οι εξής: public ArrayQueue() this(capacity); public ArrayQueue(int cap) capacity = cap; Q = new Object[capacity]; Κατά την αρχική δηµιουργία της ουράς οι τιµές των ενδεικτών first και last είναι 0. Q 0 N-1 first last 2) Εισαγωγή Στοιχείου στην Ουρά: Η διαδικασία εισαγωγής ενός στοιχείου στην ουρά συνίσταται στην τοποθέτηση του στοιχείου στη θέση του πίνακα που δείχνει ο ενδέικτης last και στη συνέχεια την αύξηση του ενδείκτη κατά ένα. Στην περίπτωση που ο χώρος της ουράς έχει εξαντληθεί από προηγούµενες εισαγωγές στοιχείων, δηµιουργείται κατάσταση εξαίρεσης (υπερχείλιση ουράς). Η µέθοδος enqueue της ArrayQueue είναι η εξής: public void enqueue(object item) if (last == capacity) throw new QueueFullException("Queue overflow"); Q[last++] = item;

Στοίβες και Ουρές 12 Η εξαίρεση QueueFullException έχει οριστεί µε τη βοήθεια ξεχωριστής κλάσης, η οποία επεκτείνει την κλάση RuntimeException. Πρέπει να σηµειωθεί ότι κατά κανόνα η κατάσταση της υπερχείλισης της ουράς είναι εικονική, µε την έννοια ότι ο πίνακας διαθέτει άδεια στοιχεία, τα οποία µε βάση τη συγκεκριµένη υλοποίηση της enqueue δεν µπορούν να χρησιµοποιηθούν. Υπάρχουν διάφοροι τρόποι αντιµετώπισης της εικονικής υπερχείλισης της ουράς, ένας από τους οποίους δίνεται στην παράγραφο 3.2.2. 3) Εξαγωγή Στοιχείου από την Ουρά: Η διαδικασία εξαγωγής ενός στοιχείου από την ουρά συνίσταται στην επιστροφή του στοιχείου που βρίσκεται στη θέση first του πίνακα και στη συνέχεια την αύξηση του ενδείκτη κατά ένα. Στην περίπτωση που η ουρά είναι άδεια, δηµιουργείται κατάσταση εξαίρεσης (άδεια ουρά). Η µέθοδος dequeue της ArrayQueue είναι η εξής: public Object dequeue( ) throws QueueEmptyException Object item; if (isempty( )) throw new QueueEmptyException("Queue is empty"); item = Q[first]; Q[first++] = null; //!!! για τον garbage collector return item; Η εξαίρεση QueueEmptyException έχει επίσης οριστεί µε τη βοήθεια ξεχωριστής κλάσης. 4) Ελεγχος κενής ουράς: Είτε στην περίπτωση που η ουρά έχει µόλις δηµιουργηθεί είτε στην περίπτωση που η ουρά δεν έχει κανένα στοιχείο µετά από διαδοχικές εισαγωγές και εξαγωγές στοιχείων οι ενδείκτες first και last θα πρέπει να έχουν την ίδια τιµή: public boolean isempty( ) return (first==last); Στο τµήµα κώδικα 7 δίνεται ολόκληρη η υλοποίηση της κλάσης ArrayQueue.

Στοίβες και Ουρές 13 public class ArrayQueue implements Queue public static final int CAPACITY = 1000; private int capacity; private Object[ ] Q; private int first = 0; private int last = 0; public ArrayQueue() this(capacity); public ArrayQueue(int cap) capacity = cap; Q = new Object[capacity]; public int size( ) return (last-first); public boolean isempty( ) return (first==last); public void enqueue(object item) if (last == capacity) throw new QueueFullException("Queue overflow"); Q[last++] = item; public Object front( ) throws QueueEmptyException if (isempty( )) throw new QueueEmptyException("Queue is empty"); return Q[first];

Στοίβες και Ουρές 14 public Object dequeue( ) throws QueueEmptyException Object item; if (isempty( )) throw new QueueEmptyException("Queue is empty"); item = Q[first]; Q[first++] = null; //!!! για τον garbage collector return item; Τµήµα Κώδικα 7 Σαν παράδειγµα χρήσης της ουράς στο τµήµα κώδικα 8 φαίνεται η χρήση µιάς ουράς για τη δηµιουργία αντιγράφου ενός πίνακα. import java.io.*; public class QueueTest public static String[ ] QCopy(String[ ] a) int i; ArrayQueue q = new ArrayQueue(); for (i=0; i<a.length; ++i) q.enqueue(a[i]); i=0; while (!q.isempty( )) a[i]=(string)q.dequeue( ); i++; return a; public static void main(string[ ] args) throws IOException String[ ] b = "nikos","demos","maria","lina","thomas"; String[ ] c = new String[b.length]; QueueTest QA = new QueueTest(); c = QA.QCopy(b); for (int i=0; i<c.length; ++i) System.out.println(c[i]); Τµήµα Κώδικα 8

Στοίβες και Ουρές 15 3.2.2 Κυκλική Ουρά Όπως αναφέρθηκε στην παράγραφο 3.2.1 η υλοποίηση της ουράς µε τη βοήθεια ενός πίνακα µπορεί να οδηγήσει στο φαινόµενο της εικονικής υπερχείλισης. Ας θεωρήσουµε για παράδειγµα ότι έχουµε εισάγει και στη συνέχεια εξάγει ένα στοιχείο Ν-1φορές στην ουρά. Στην περίπτωση αυτή οι ενδείκτες first και last θα έχουν την τιµή Ν και η επόµενη προσπάθεια εισαγωγής στοιχείου στην ουρά θα οδηγηθεί σε αποτυχία, παρά το γεγονός ότι ο πίνακας που φιλοξενεί τα στοιχεία είναι εντελώς άδειος! Λύση στο πρόβληµα αυτό αποτελεί η κυκλική ουρά. Στην περίπτωση αυτή θεωρούµε ότι το επόµενο στοιχείο του Q[N-1] στον πίνακα είναι το Q[0] εφόσον αυτό είναι κενό. Για να γίνει δυνατή η υλοποίηση της κυκλικής ουράς πρέπει στις πράξεις εισαγωγής και εξαγωγής στοιχείου να τροποποιήσουµε την αύξηση των ενδεικτών µε (first+1)modulon και (last+1)modulon αντίστοιχα, όπου ο τελεστής modulo αντιστοιχεί στο υπόλοιπο της ακεραίας διαίρεσης (στη Java χρησιµοποιείται ο τελεστής %). Μία εικόνα της κυκλικής ουράς µετά από έναν αριθµό πράξεων εισαγωγής - εξαγωγής φαίνεται στο παρακάτω σχήµα: Q 0 N-1 last first Στο τµήµα κώδικα 9 δίνεται η κλάση SArrayQueue που υλοποιεί τον τύπο Queue σαν µία κυκλική ουρά.

Στοίβες και Ουρές 16 public class SArrayQueue implements Queue // Υλοποίηση κυκλικής ουράς public static final int CAPACITY = 1000; private int capacity; private Object[ ] Q; private int first = 0; private int last = 0; public SArrayQueue( ) this(capacity); public SArrayQueue(int cap) capacity = cap; Q = new Object[capacity]; public int size( ) return (capacity-first+last)%capacity; public boolean isempty( ) return (first==last); public void enqueue(object item) if (size( ) == capacity) throw new QueueFullException("Queue overflow"); Q[last] = item; last = (last+1)%capacity; public Object front( ) throws QueueEmptyException if (isempty()) throw new QueueEmptyException("Queue is empty"); return Q[first];

Στοίβες και Ουρές 17 public Object dequeue( ) throws QueueEmptyException Object item; if (isempty( )) throw new QueueEmptyException("Queue is empty"); item = Q[first]; Q[first] = null; //!!! για τον garbage collector first=(first+1)%capacity; return item; Τµήµα Κώδικα 9

Στοίβες και Ουρές 18 3.3 ΑΣΚΗΣΕΙΣ 1. Δώστε το περιεχόµενο της στοίβας (stack) µετά την εκτέλεση της παρακάτω σειράς από πράξεις: Π Α Ρ * Α * * Π Ο Λ * * * Υ Ε * * * Υ * Κ Ο * Λ Ο * Στην περίπτωση αυτή ένα γράµµα ερµηνεύεται σαν πράξη εισαγωγής (του γράµµατος) στη στοίβα, ενώ ο χαρακτήρας * ερµηνεύεται σαν πράξη εξαγωγής (ενός γράµµατος) από τη στοίβα. 2. Αν η εκτέλεση της σειράς των πράξεων της άσκησης 1 γινόταν σε µία ουρά (queue), ποιό θα ήταν το τελικό της περιεχόµενο; 3. Υλοποιήστε τη δοµή δεδοµένων στοίβα (stack) µε τη χρήση της κλάσης Vector της Java. 4. Μία αριθµητική παράσταση περιέχει παρενθέσεις. Να γραφεί ένα πρόγραµµα το οποίο να ελέγχει εάν οι παρενθέσεις αυτές έχουν χρησιµοποιηθεί σωστά. Να χρησιµοποιηθεί η δοµή δεδοµένων στοίβα. 5. Περιγράψτε πως είναι δυνατή η υλοποίηση µίας στοίβας µε τη βοήθεια δύο ουρών. 6. Υλοποιήστε τη δοµή δεδοµένων ουρά (queue) µε τη χρήση της κλάσης Vector της Java. 7. Περιγράψτε πως είναι δυνατή η υλοποίηση µίας ουράς µε τη βοήθεια δύο στοιβών. 8. Στην περίπτωση της κυκλικής ουράς που παρουσιάστηκε στην παράγραφο 3.2.2 θεωρήστε ότι έχουµε εισάγει Ν στοιχεία, χωρίς να έχουµε εξάγει κανένα. Θα έχουµε τότε first = last όπως ακριβώς και στην περίπτωση που η ουρά είναι άδεια. Πως µπορούµε να αντιµετωπίσουµε αυτό το πρόβληµα;

υναµικές οµές εδοµένων 1 4 ΔYNAMIKEΣ ΔOMEΣ ΔEΔOMENΩN Yπάρχει ένας αριθµός προγραµµατιστικών προβληµάτων, τα οποία είναι δύσκολο να επιλυθούν µε τη βοήθεια των στατικών δοµών δεδοµένων που αναφέραµε µέχρι τώρα. Aυτό γιατί οι δοµές αυτές είναι προκαθορισµένες και παραµένουν σταθερές σε όλη τη διάρκεια της εκτέλεσης του προγράµµατος. Για παράδειγµα εάν θεωρήσουµε την παρακάτω δήλωση της µεταβλητής A. int[ ] A = new int[1000]; Ο αριθµός των στοιχείων του πίνακα Α που είναι διαθέσιµα κατά τη διάρκεια εκτέλεσης του προγράµµατος θα είναι ακριβώς 1000, κάτι που προκαθορίζεται όταν το πρόγραµµα µεταγλωττίζεται. Eπί πλέον ο χώρος µνήµης που απαιτείται για την αποθήκευση των στοιχείων αυτών είναι επίσης προκαθορισµένος. H κατάσταση αυτή δεν αποτελεί πρόβληµα στην περίπτωση που γνωρίζουµε από την αρχή πόσα στοιχεία θα χρειαστούµε, αυτό όµως τις περισσότερες φορές δεν είναι εφικτό. Ένα δεύτερο πρόβληµα δηµιουργείται από το γεγονός ότι τα στοιχεία του πίνακα θεωρούνται συνεχόµενα. Έτσι εάν χρειαστεί να παρεµβάλουµε ένα στοιχείο ανάµεσα σε άλλα δύο (ανάγκη που προκύπτει, όταν θέλουµε για παράδειγµα να εισάγουµε µία τιµή σε έναν ταξινοµηµένο πίνακα χωρίς να αλλοιώσουµε την ιδιότητά του αυτή) πρέπει να καταφύγουµε στη χρονοβόρα διαδικασία αντιγραφής ενός µεγάλου πλήθους στοιχείων σε άλλη περιοχή του πίνακα. H λύση στα προβλήµατα αυτά µπορεί να δοθεί µε τη χρήση των δυναµικών δοµών δεδοµένων.

υναµικές οµές εδοµένων 2 Mια δοµή δεδοµένων η οποία µπορεί να δηµιουργηθεί και να µετατρέπεται στη διάρκεια της εκτέλεσης του προγράµµατος αυξοµειώνοντας το µέγεθός της ή το χώρο που καταλαµβάνει στη µνήµη ονοµάζεται δυναµική δοµή δεδοµένων (dynamic data structure ). Μία τέτοια δυναµική δοµή δεδοµένων είναι η συνδεδεµένη λίστα (linked list), που εξετάζουµε στη συνέχεια. 4.1 ΛΙΣΤΕΣ Λίστα (list) είναι ένα διατεταγµένο σύνολο από 0 ή περισσότερα στοιχεία τα οποία, κατά κανόνα, είναι όλα του ίδιου τύπου. Μία λίστα διαφέρει από έναν πίνακα στο ότι το µέγεθός της είναι γενικά µεταβλητό. Συχνά παριστάνουµε µία λίστα παραθέτοντας τα στοιχεία της ως εξής: a 1, a 2,..., a n όπου n >= 0 και κάθε a i είναι κάποιου συγκεκριµένου τύπου. Ο αριθµός των στοιχείων της λίστας ονοµάζεται µήκος (length) της λίστας. Θεωρώντας ότι n >= 1, λέµε ότι το a 1 είναι το πρώτο στοιχείο της λίστας και το a n το τελευταίο. Εάν n = 0, τότε έχουµε την κενή λίστα (µία λίστα που δεν έχει κανένα στοιχείο). 4.1.1 Yλοποίηση Λίστας µε τη Bοήθεια Δεικτών Mια λίστα µπορεί να θεωρηθεί σαν µία συλλογή από κόµβους, οι οποίοι είναι συνδεδεµένοι µεταξύ τους. Kάθε κόµβος µπορεί να υλοποιηθεί µε τη βοήθεια µιας δυάδας πεδίων. Το πρώτο πεδίο περιλαµβάνει ένα στοιχείο της λίστας (την πληροφορία που σχετίζεται µε το στοιχείο αυτό) και το δεύτερο ένα δείκτη ο οποίος δείχνει στον επόµενο κόµβο της λίστας. Τα δύο αυτά πεδία µπορούν να οριστούν στα πλαίσια της κλάσης Node, η οποία δίνεται στη συνέχεια. Η δηµιουργία ενός καινούργιου κόµβου µπορεί να περιλαµβάνει αρχικά την περίπτωση ενός κενού κόµβου, ο οποίος δεν έχει καµία πληροφορία στο πεδίο item (= null) και δεν είναι συνδεδεµένος µε κανέναν άλλο κόµβο (next = null). Μπορεί όµως να περιλαµβάνει την περίπτωση που και τα δύο αυτά πεδία έχουν συγκεκριµένες τιµές κατά τη δηµιουργία τους.

υναµικές οµές εδοµένων 3 class Node Object item; Node next; public Node( ) this(null,null); public Node(Object it, Node n) item = it; next = n; Oι λίστες που υλοποιούνται µε αυτό τον τρόπο ονοµάζονται συνδεδεµένες λίστες (linked lists). Η σχηµατική αναπαράσταση µίας τέτοιας λίστας η οποία περιλαµβάνει τρεις κόµβους δίνεται στο σχήµα 4.1: L null Σχήµα 4.1 : Συνδεδεµένη λίστα µε τρεις κόµβους Πρέπει να τονιστεί ότι στις συνδεδεµένες λίστες δεν υπάρχει άµεσος τρόπος πρόσβασης σε έναν οποιονδήποτε κόµβο της. Ο µόνος που «γνωρίζει» την ύπαρξη ενός κόµβου είναι ο προηγούµενός του, καθώς γνωρίζει τη διεύθυνση του σαν τιµή που είναι αποθηκευµένη στο πεδίο του next. Με την έννοια αυτή για να έχουµε πρόσβαση σε έναν κόµβο πρέπει να περάσουµε ακολουθιακά από όλους τους προηγούµενους αρχίζοντας από τον πρώτο. Ο πρώτος κόµβος της λίστας πρέπει οπωσδήποτε να «δεικνύεται» από µία µεταβλητή τύπου Node (η µεταβλητή L στο παράδειγµα του σχήµατος 4.1). Η µεταβλητή αυτή ονοµάζεται δείκτης (pointer). Στην περίπτωση της Java ο δείκτης αυτός δεν είναι τίποτε άλλο από µία µεταβλητή τύπου αναφοράς (reference), µία µεταβλητή που

υναµικές οµές εδοµένων 4 αναπαριστά ένα αντικείµενο της κλάσης Node. O δείκτης στον πρώτο κόµβο χαρακτηρίζει τη συνδεδεµένη λίστα σαν δοµή δεδοµένων και πρέπει να παραµένει εκεί σε όλη τη διάρκεια εκτέλεσης του προγράµµατος που τη χειρίζεται. Δείκτες είναι επίσης όλες οι τιµές των πεδίων next όλων των κόµβων της συνδεδεµένης λίστας: δείχνουν στον επόµενο κόµβο. Το πεδίο του τελευταίου κόµβου έχει την τιµή null (δείχνει στο null). Στο παράδειγµα του σχήµατος 4.1: L.next είναι ο δείκτης που δείχνει στο δεύτερο κόµβο και L.next.next είναι ο δείκτης που δείχνει στον τρίτο κόµβο Οι πιο βασικές λειτουργίες που απαιτούνται για το χειρισµό µιας συνδεδεµένης λίστας ανήκουν σε µία από τις παρακάτω τρεις κατηγορίες: Επίσκεψη κάποιου κόµβου της συνδεδεµένης λίστας Προσθήκη ενός κόµβου στη συνδεδεµένη λίστα Διαγραφή ενός κόµβου από τη συνδεδεµένη λίστα Πρόσβαση σε κάποιο κόµβο της λίστας: Για να επισκεφθούµε κάποιον συγκεκριµένο κόµβο µίας λίστας (για να έχουµε πρόσβαση σ αυτόν) εργαζόµαστε ως εξής: Αρχικά τοποθετούµε έναν βοηθητικό δείκτη ο οποίος δείχνει στην αρχή της λίστας (σχ. 4.2α) Current = L; L Current... null Σχήµα 4.2α : Ο βοηθητικός δείκτης Current δείχνει στην αρχή της λίστας Στη συνέχεια µετακινούµε τον δείκτη Current στον επόµενο κόµβο Current = Current.next; ή ισοδύναµα (βλέπε κλάση Node στο τµήµα κώδικα 1) Current = Current.getNext( );

υναµικές οµές εδοµένων 5 Η παραπάνω διαδικασία µετακίνησης στον επόµενο κόµβο γίνεται επαναληπτικά όσες φορές χρειάζεται, ώστε να τοποθετηθούµε στον επιθυµητό κόµβο (σχ. 4.2β). L Current... null Σχήµα 4.2β : Ο βοηθητικός δείκτης Current δείχνει στον επιθυµητό κόµβο Εισαγωγή ενός νέου κόµβου στη λίστα: Για να τοποθετήσουµε έναν κόµβο σε µία συνδεδεµένη λίστα πρέπει να γνωρίζουµε τον προηγούµενό του ή ισοδύναµα να τον δείχνουµε µε τη βοήθεια ενός δείκτη τύπου Node, έστω C στο παράδειγµα του σχήµατος 4.3α. Στη συνέχεια η εισαγωγή ακολουθεί τα εξής βήµατα: Αρχικά δηµιουργούµε έναν καινούργιο κόµβο (σχ.4.3α) Β = new Node(T, null) όπου T η τιµή που θέλουµε να έχει το πεδίο πληροφορίας item του κόµβου. B C...... Σχήµα 4.3α : ηµιουργία του κόµβου Β προς εισαγωγή Στη συνέχεια συνδέουµε τον κόµβο αυτό µε τον επόµενο του C (σχ. 4.3β) Β.next = C. next; ή ισοδύναµα (βλέπε κλάση Node στο τµήµα κώδικα 1) B.setNext(C.getNext( ));

υναµικές οµές εδοµένων 6 B C...... Σχήµα 4.3β : Ο επόµενος του κόµβου Β γίνεται ο επόµενος του C Τέλος κάνουµε επόµενο του κόµβου C τον κόµβο B (σχ. 4.3γ) C.next = B; ή ισοδύναµα C.setNext(B); B C...... Σχήµα 4.3γ : Ο επόµενος του κόµβου C γίνεται ο νέος κόµβος Β Διαγραφή ενός κόµβου από τη λίστα: Για να διαγράψουµε έναν κόµβο από µία συνδεδεµένη λίστα πρέπει επίσης, όπως και στην περίπτωση της εισαγωγής, να γνωρίζουµε τον προηγούµενό του ή ισοδύναµα να τον δείχνουµε µε τη βοήθεια ενός δείκτη τύπου Node, έστω C στο παράδειγµα του σχήµατος 4.4α. Στη συνέχεια διαγράφουµε τον επόµενο κόµβο του C παρακάµπτοντάς τον ως εξής: C.next = C.next.next; ή ισοδύναµα C.setNext(C.getNext( ).getnext( )) Η κατάσταση της λίστας πριν και µετά τη διαγραφή φαίνεται στα σχήµατα 4.4α και 4.4β. C...... Σχήµα 4.4α : Πριν τη διαγραφή του επόµενου κόµβου του C

υναµικές οµές εδοµένων 7 C...... Σχήµα 4.4β : Μετά τη διαγραφή του επόµενου κόµβου του C Στα σχήµατα 4.5α,β και γ φαίνονται τρεις διαφορετικοί τρόποι αναπαράστασης µιας συνδεδεµένης λίστας. Οι δύο πρώτοι αναφέρονται στην απλά συνδεδεµένη λίστα, που έχουµε συζητήσει. L... null Σχήµα 4.5α : Απλά συνδεδεµένη λίστα µε πρόσβαση στην αρχή Στη δεύτερη περίπτωση φροντίζουµε επιπλέον να διατηρούµε έναν δείκτη στον τελευταίο κόµβο, για να έχουµε έτσι ευκολότερη πρόσβαση σε αυτόν. firstnode lastnode... null Σχήµα 4.5β : Απλά συνδεδεµένη λίστα µε πρόσβαση στην αρχή και στο τέλος Στην τρίτη περίπτωση αναφερόµαστε στη διπλά συνδεδεµένη λίστα, όπου κάθε κόµβος της διαθέτει ένα επιπλέον πεδίο τύπου Node το οποίο δείχνει στον προηγούµενο κόµβο της λίστας.

υναµικές οµές εδοµένων 8 firstnode lastnode null... null Σχήµα 4.5γ : ιπλά συνδεδεµένη λίστα Στη συνέχεια δίνεται η υλοποίηση της συνδεδεµένης λίστας του σχήµατος 4.5β: Για τη λίστα αυτή ορίζουµε δύο δείκτες firstnode και lastnode που δείχνουν αντίστοιχα στον πρώτο και τελευταίο κόµβο της. Στην περίπτωση της κενής λίστας θεωρούµε ότι οι δύο αυτοί δείκτες δείχνουν στο null. Υπεύθυνη για τη διαχείριση της συνδεδεµένης λίστας είναι η κλάση LinkedList, στα πλαίσια της οποίας ορίζονται οι πράξεις: insertfirst: εισάγει ένα στοιχείο στην αρχή της λίστας insertlast: εισάγει ένα στοιχείο στο τέλος της λίστας removefirst: διαγράφει και επιστρέφει το πρώτο στοιχείο της λίστας removelast: διαγράφει και επιστρέφει το τελευταίο στοιχείο της λίστας isempty: ελέγχει εάν η λίστα είναι κενή size: επιστρέφει το µέγεθος (αριθµό κόµβων) της λίστας printlist: εκτυπώνει όλα τα στοιχεία της λίστας Στο τµήµα κώδικα 1 δίνεται η αναπαράσταση/υλοποίηση ενός κόµβου της συνδεδεµένης λίστας και στο τµήµα κώδικα 2 δίνεται η κλάση που υλοποιεί τη συνδεδεµένη λίστα. Θεωρούµε ότι οι κλάσεις Node και LinkedList βρίσκονται στο ίδιο πακέτο. Με την έννοια αυτή η κλάση LinkedList έχει πρόσβαση στα πεδίa item και next της Node. Εναλλακτικά, και για µια πιο ασφαλή υλοποίηση, θα έπρεπε: Τα πεδία item και next της κλάσης Node να δηλωθούν private και Στα πλαίσια της κλάσης LinkedList να γίνεται χρήση των πεδίων αυτών µε τη βοήθεια των µεθόδων getnext, setnext, getitem και setitem.

υναµικές οµές εδοµένων 9 class Node //Ορισµός κόµβου µιας απλά συνδεδεµένης λίστας (linked list) Object item; Node next; public Node( ) this(null,null); public Node(Object it, Node n) item = it; next = n; public void setitem(object newitem) item = newitem; public void setnext(node newnext) next = newnext; public Object getitem( ) return(item); public Node getnext( ) return(next); Τµήµα Κώδικα 1

υναµικές οµές εδοµένων 10 import java.io.*; public class LinkedList //Υλοποίηση µιάς απλά συνδεδεµένης λίστας (linked list) private Node firstnode, lastnode; public LinkedList( ) firstnode = lastnode = null; public Node getfirst( ) return firstnode; public Node getlast( ) return lastnode; public boolean isempty( ) return(firstnode == null); public void insertfirst(object newitem) if (isempty( )) firstnode = lastnode = new Node(newItem, null); else firstnode = new Node(newItem, firstnode); public void insertlast(object newitem) if (isempty( )) firstnode = lastnode = new Node(newItem, null); else lastnode = lastnode.next = new Node(newItem, null);

υναµικές οµές εδοµένων 11 public Object removefirst( ) throws ListEmptyException Object removeitem; if (isempty( )) throw new ListEmptyException("Empty List!!!"); removeitem = firstnode.item; if (firstnode.equals(lastnode)) firstnode = lastnode = null; else firstnode = firstnode.next; return removeitem; public Object removelast( ) throws ListEmptyException Object removeitem; if (isempty( )) throw new ListEmptyException("Empty List!!!"); removeitem = lastnode.item; if (firstnode.equals(lastnode)) firstnode = lastnode = null; else Node current = firstnode; while (current.next!= lastnode) current = current.next; lastnode = current; current.next = null; return removeitem; public void printlist( ) if (isempty( )) System.out.println("Empty List"); else Node current = firstnode; while (current!= null) System.out.print(current.item.toString() + " "); current = current.next;

υναµικές οµές εδοµένων 12 System.out.println("\n"); ------------- public class ListEmptyException extends RuntimeException public ListEmptyException(String err) super(err); Τµήµα Κώδικα 2 Στο τµήµα κώδικα 3 δίνεται η υλοποίηση της µεθόδου για την ταξινόµηση των στοιχείων µιας συνδεδεµένης λίστας. public class ListSort extends LinkedList; public LinkedList SortList() Node trace, current, min; trace = getfirst(); while (trace!=null) current = trace; min = trace; while (current!=null) if ((current.getitem()).compareto(min.getitem())<0) min=current; current = current.getnext(); string temp = trace.getitem(); trace.setitem(min.getitem()); min.setitem(temp); trace = trace.getnext(); //endwhile Τµήµα Κώδικα 3