Τεχνολογικό Εκπαιδευτικό Ίδρυμα Κρήτης Σχολή Εφαρμοσμένων Επιστημών Τμήμα Ηλεκτρονικών Μηχανικών Τομέας Αυτοματισμού και Πληροφορικής Δομημένος Προγραμματισμός (ΤΛ100) Δρ. Μηχ. Νικόλαος Πετράκης, Καθηγητής Εφαρμογών (npet@chania.teicrete.gr) Δωδέκατη (1 η ) τρίωρη διάλεξη. Ιστοσελίδα Μαθήματος: https://eclass.chania.teicrete.gr/courses/el10 Εξάμηνο: Χειμερινό 01-18
Δομές δεδομένων και δείκτες διεύθυνσης (Δείκτες ως μέλη δομών) Οι δείκτες μπορούν να αποτελέσουν μέλη δομών struct data { } first; int *value; int *rate; Εδώ δηλώνουμε μία δομή της οποίας τα δύο μέλη είναι δείκτες προς τύπο int. Πρέπει να δώσουμε αρχικές τιμές: int cost=, interest=1; first.value = &cost; first.rate = &interest; Η έκφραση *first.value έχει την τιμή της cost και ανάλογα η *first.rate έχει την τιμή της interest.
Δομές δεδομένων και δείκτες διεύθυνσης (Δείκτες ως μέλη δομών) O δείκτης που χρησιμοποιείται πιο συχνά ως μέλος δομής είναι ο δείκτης προς τύπο char. Μία συμβολοσειρά είναι μία ακολουθία χαρακτήρων που περιγράφεται από ένα δείκτη που δείχνει στον πρώτο χαρακτήρα και από ένα μηδενικό χαρακτήρα που δηλώνει το τέλος της συμβολοσειράς. char *p_message; p_message = "Teach yourself C in 1 days"; struct msg { char *p1; char *p; } myptrs; myptrs.p1 = "Teach yourself C in 1 days"; myptrs.p = "by SAMS Publishing"; printf("%s %s", myptrs.p1, myptrs.p);
Δομές δεδομένων και δείκτες διεύθυνσης (Δείκτες ως μέλη δομών) Σε αυτή τη δομή βλέπουμε μεθόδους αποθήκευσης μίας συμβολοσειράς σε μία δομή. struct msg { char p1[0]; char *p; } myptrs; strcpy(myptrs.p1, "Teach yourself C in 1 days"); strcpy(myptrs.p, "by SAMS Publishing"); Περιορισμένος χώρος στη μνήμη /*πρόσθετος κώδικας*/ puts(myptrs.p1); puts(myptrs.p);
Δομές δεδομένων και δείκτες διεύθυνσης (Δείκτες σε δομές) struct part { }; int number; char name[10]; Η p_part είναι ένας δείκτης σε τύπο part και όχι ένα στιγμιότυπο τύπου part. struct part *p_part; Μεταβλητή τύπου δομής struct part va; Εκχωρεί τη διεύθυνση της va στον p_part) p_part = &va; (*p_part).number = 100; p_part->number = 100; va.number = 100; Εκχωρεί τη τιμή 100 στη va.number Το ίδιο με πριν, με τον «τελεστή βέλος» έμμεσης σχέσης μέλους, (arrow operator) Ισοδύναμες προτάσεις 5
Δείκτες και διατάξεις δομών struct part { int number; char name[10]; } Αφού ορίσουμε τη δομή part, μπορούμε να δηλώσουμε μία διάταξη τύπου part: struct part data[100]; Μετά μπορούμε να δηλώσουμε ένα δείκτη διεύθυνσης σε τύπο part και να του δώσουμε ως αρχική τιμή να δείχνει στην πρώτη δομή (δηλαδή το πρώτο στοιχείο) της διάταξης data: struct part *p_part; p_part = &data[0]; // ή καλύτερα: p_part = data; Προβολή του περιεχομένου της πρώτης δομής της διάταξης δομών, με pointer: printf("%d %s\n", p_part->number, p_part->name); ή ισοδύναμα με πίνακα και δείκτη πίνακα: printf("%d %s\n", data[0].number, data[0].name);
Τεχνικές ταξινόμησης (συνέχεια μετά την ταξινόμηση κατ επιλογή)
Μέθοδος φυσαλίδας (bubble sort) Αυτή η μέθοδος ταξινόμησης σαρώνει κατ επανάληψη τον πίνακα από την αρχή προς το τέλος συγκρίνοντας ανά δύο όλα τα στοιχεία που βρίσκονται σε διαδοχικές θέσεις και αν δεν είναι σωστά τοποθετημένα το ένα σε σχέση με το άλλο, τα αντιμεταθέτει. Εάν σε ένα ολόκληρο σάρωμα από την αρχή ως το τέλος δεν χρειαστεί να γίνει καμιά αντιμετάθεση, τερματίζεται η ταξινόμηση διότι έχει ολοκληρωθεί. 8
Μέθοδος φυσαλίδας (bubble sort) Αρχή πίνακα Τέλος πίνακα
1 ο Παράδειγμα (ταξινόμηση πίνακα ακεραίων) Να γίνει πρόγραμμα το οποίο να γεμίζει ένα πίνακα 0 ακεραίων με ψευδοτυχαίες τιμές από το κλειστό διάστημα [-0, 10], να εμφανίζει τον πίνακα στην οθόνη σε μία γραμμή, να τον ταξινομεί σε αύξουσα διάταξη και να τον εμφανίζει εκ νέου. Μοιράστε τον κώδικα σε συναρτήσεις όσο μπορείτε πιο αποτελεσματικά. Η πλήρης λύση δόθηκε στον πίνακα. 10
Μέθοδος φυσαλίδας (bubble sort) void taxin(proin x[]) { int i, top, top1, tax_ok; Proion h; top= N-1; do { tax_ok = TRUE; for (i=0; i<top ; i++) if (x[i].po<x[i+1].po) { h=x[i]; x[i]=x[i+1]; x[i+1]=h; } tax_ok=false; top1=i; } top=top1; }while (tax_ok==false); return; Συνάρτηση ταξινόμησης των προϊόντων σε φθίνουσα διάταξη ως προς την ποσότητα (συμπληρωματικός κώδικας σε παλιότερο παράδειγμα που είχε γίνει στην τάξη) Στην αρχή του κώδικα θα πρέπει να έχουν δηλωθεί τα συμβολικά ονόματα (ακέραιες σταθερές) FALSE και TRUE, π.χ.: #define FALSE 0 #define TRUE 1 11
Μέθοδοι Αναζήτησης (Searching) Στόχος των αλγόριθμων αναζήτησης είναι η πιο αποτελεσματική συλλογή πληροφοριών που είναι αποθηκευμένες στην μνήμη ενός Η/Υ. Το πρόβλημα της αναζήτησης, στην πιο γενική του μορφή, αναφέρεται στην αναζήτηση κάποιου στοιχείου ενός συνόλου, που ικανοποιεί μια συνθήκη. 1
Σειριακή αναζήτηση Η πιο απλή μέθοδος αναζήτησης είναι αυτή της σειριακής αναζήτησης (sequential search) όπου συγκρίνονται διαδοχικά τα κλειδιά, των αντικειμένων του συνόλου στο οποίο διενεργείται η αναζήτηση, με το ζητούμενο κλειδί. Η διαδικασία τερματίζεται όταν διαπιστωθεί ότι κάποιο από τα κλειδιά είναι ίσο με το ζητούμενο ή όταν εξετασθούν όλα τα κλειδιά. Η συνάρτηση seqsearch που ακολουθεί υλοποιεί τον παραπάνω αλγόριθμο. Για να απλοποιηθεί η συνθήκη ελέγχου της εντολής επανάληψης ο αλγόριθμος προτείνει την καταχώρηση του κλειδιού μια θέση μετά το τέλος του πίνακα. 1
Παράδειγμα σειριακής αναζήτησης #include <stdio.h> #include <stdlib.h> #include <time.h> #define N 0 #define L 10 #define K 50 int seqsearch (int *a, int n, int key); Γέμισμα πίνακα με ψευδοτυχαίες ακέραιες τιμές από το 10 έως το 50 και αναζήτηση της τιμής 0. // Μέγεθος πίνακα ακεραίων // Κάτω όριο και // άνω όριο ψευδοτυχαίων τιμών main() { int i,thesh, pin[n+1]; // Μια παραπάνω θέση srand((unsigned)time(null)); for (i=0; i<n; i++) pin[i]= L + rand()%(k-l+1); thesh = seqsearch(pin, N, 0); if (thesh > -1) printf("found at position %d\n\n",thesh); else printf("not FOUND.\n\n"); sestem("pause"); return 0; } 1
Παράδειγμα σειριακής αναζήτησης int seqsearch (int *a, int n, int key) { int d=0; a[n]=key; while (a[d]!=key) d++; if (d==n) return -1; // Δεν υπάρχει else return d; // Υπάρχει στην θέση d } Όταν συμβαίνουν συχνά ανεπιτυχείς αναζητήσεις είναι προτιμότερο να ταξινομούμε τα κλειδιά και να εφαρμόζουμε έναν τροποποιημένο σειριακό αλγόριθμο αναζήτησης. int seqsearch(int *a, int n, int key) { int d=0; a[n]=int_max; // INT_MAX = 18 while (a[d]<key) // Maximum (signed) int value d++; if (d<n && a[d]==key) return d // Υπάρχει στην θέση d else return -1; // Δεν υπάρχει } 15
Πολυπλοκότητα σειριακής αναζήτησης Για τεχνικές σειριακής αναζήτησης ισχύει ότι αν έχουμε n στοιχεία, στην χειρότερη περίπτωση θα εκτελεστούν (n+1) συγκρίσεις, στην μέση περίπτωση έχουμε (n+1)/, και στην καλύτερη έχουμε 1 σύγκριση. Με άλλα λόγια η πολυπλοκότητα της μέσης και της χειρότερης περίπτωσης είναι O(n). Υπάρχουν όμως κι άλλοι αλγόριθμοι που είναι ταχύτεροι (ειδικά για μεγάλο αριθμό στοιχείων), οι οποίοι όμως υποθέτουν ότι το σύνολο των στοιχείων είναι γραμμικώς διατεταγμένο ως προς το κλειδί της αναζήτησης. 1
Δυαδική αναζήτηση (binary search) Η μέθοδος αυτή, η οποία μπορεί να εφαρμοστεί μόνο σε ταξινομημένα κλειδιά, διχοτομεί σε κάθε επανάληψη το μέρος του πίνακα που δεν έχει διερευνηθεί και περιορίζει την αναζήτηση στον μισό πίνακα. Έτσι μετά από lgn διχοτομήσεις, στην χειρότερη περίπτωση, είτε θα έχουμε εντοπίσει το ζητούμενο κλειδί είτε θα έχουμε καταλήξει ότι το κλειδί αυτό δεν υπάρχει. Επειδή, ο μέγιστος αριθμός συγκρίσεων που απαιτούνται για την αναζήτηση ενός κλειδιού είναι ο λογάριθμος με βάση το του συνολικού αριθμού n των κλειδιών, η διαδικασία αυτή ονομάζεται και λογαριθμική αναζήτηση (logarithmic search). 0 1 n- n-1 bot mid = (bot + top)/ top 1
Παράδειγμα δυαδικής αναζήτησης Η συνάρτηση binsearch που ακολουθεί αποτελεί την υλοποίηση του παραπάνω αλγόριθμου σε μη αναδρομική μορφή. int binsearch(int *a, int n, int key) { int bot, top, mid; bot = 0; top = n-1; while (bot <= top) { mid = (bot + top)/; if (key < a[mid]) top = mid - 1; else if (key > a[mid]) bot = mid + 1; else return mid; } return -1; } 18