De Mauro, Fazio - ADT Andrea De Mauro Maurizio Fazio Abstract Data Type 1 De Mauro, Fazio - ADT Indice Introduzione 3 Definizione 3 Implementazione 4 Strutture dati principali Lista circolare Stack Coda 5 5 9 13 Link utili 16 2 De Mauro, Fazio - ADT Introduzione In ogni linguaggio di programmazione vengono utilizzati dei tipi di dati (data types) caratterizzati dall’avere un certo livello di complessità organizzativa: nel C, ad esempio, siamo abituati a lavorare con numeri interi con o senza segno (unsigned int, int), con numeri in virgola mobile (float) e così via i quali rappresentano i tipi di dati semplici; abbiamo, inoltre, a disposizione tipi di dati più complessi derivanti dalla “composizione” di quelli semplici detti strutture (struct). Per ognuno di questi tipi di dati è definito un insieme di funzioni che agiscono su di essi (ovvero che hanno come domini i tipi di dati suddetti). Queste funzioni sono caratterizzate dal fatto che il programmatore normalmente le utilizza pur non conoscendone il comportamento interno: una caratteristica di queste funzioni è, quindi, la loro versatilità e facilità d’uso. Senza utilizzare le funzioni, un programma non banale sarebbe molto più difficile da scrivere e debuggare. Inoltre le funzioni non dipendono dal tipo di compilatore utilizzato e, nel caso fosse necessario, possono essere modificate senza che il programmatore se ne accorga (chiaramente, se i parametri e i valori restituiti restano uguali). Le più importanti basi di dati utilizzate nei programmi non sono in numero molto elevato. Ci si trova spesso a dover utilizzare, infatti, per problemi diversi la stessa base di dati e, di conseguenza, funzioni molto simili tra loro. Se riuscissimo ad avere a disposizione dei set di funzioni che permettano di gestire integralmente le più importanti strutture per l’organizzazione di dati (ad esempio pile, code, liste, grafi ad albero e così via) il nostro lavoro diventerebbe più veloce e ordinato: potremmo, infatti, utilizzare sempre le stesse funzioni (accuratamente testate e, quindi, sicuramente prive di errori), preoccupandoci di gestirne solo i parametri e i valori restituiti (di norma codici di errore). Ecco che diviene, allora, indispensabile la definizione del tipo di dato astratto (abstract data type). Definizione Secondo l’autorevole definizione del NIST (national institute of standards and technology) [1], il tipo di dato astratto e definito come “una collezione di entità matematicamente definite per la memorizzazione di dati e delle operazioni per la loro creazione, modifica, ecc.” (a mathematically specified collection of data-storing entities with operations to create, access, change, etc. instances). L’aggettivo “astratto” sta ad indicare che, per la definizione degli ADT, occorre considerare dei problemi di carattere generale e creare un modello logico-matematico che li rappresenti tramite un’operazione di astrazione. 3 De Mauro, Fazio - ADT Un’ADT sarà costituito dalla struttura con la quale organizzare i dati, ovvero la struttura di dati astratti (abstract data structure), e un insieme di funzioni che “ci lavorano sopra” detto interfaccia (interface). Abbiamo detto che questi ADT sono definiti matematicamente. Una definizione rigorosa potrebbe essere la seguente [2]: Definizione di ADT Un tipo di dato astratto, o ADT, e' una tripla {S, F, C} tale che: S è l'insieme dei domini {v1, v2, .., vn}, tra i quali viene individuato un dominio speciale, chiamato dominio di interesse, che rappresenta l'insieme dei valori del tipo che si sta definendo; F è l'insieme delle funzioni, o operazioni primitive, {f1, f2, …, fn} che possono essere eseguite sul tipo che si sta definendo. Formalmente, ciascuna fi è tale che il suo dominio ed il suo codominio sono formati da domini contenuti in S, con il vincolo che il dominio di interesse o è il codominio di f i oppure appare tra gli insiemi che definiscono il suo dominio; C è l'insieme degli elementi che denotano valori di particolare importanza del dominio di interesse. Implementazione Per definizione, un ADT deve essere creato fissando gli insiemi S, F e C in ottemperanza alle caratteristiche del linguaggio di programmazione utilizzato. Questo lavoro deve essere svolto ricordando le prerogative principali dei tipi di dati astratti: essi dovranno essere implementabili in più situazioni possibili, la loro interfaccia dovrà essere di facile comprensione (magari corredati anche di guida), dovranno essere efficienti in termini di risorse (minore complessità possibile) e, inoltre, dovranno essere facilmente modificabili. In termini di linguaggio C, quest’ultima esigenza può essere gestita con la creazione di file .h che contengano la definizione delle funzioni dell’interfaccia: nel caso in cui, per qualche motivo, occorra modificare queste funzioni, non occorrerà ritoccare il programma utente già scritto ma sarà sufficiente ricompilarlo dopo aver modificato il file .h. 4 De Mauro, Fazio - ADT Strutture dati principali Lista circolare Le liste rappresentano una fra le più semplici strutture dati utilizzate per la memorizzazione e la gestione di informazioni. Il vantaggio del loro utilizzo è la semplicità della gestione. Di contro le prestazioni delle operazioni necessarie a gestire tali strutture dati non sono eccelse. Se n è il numero di informazioni contenute, la complessità computazionale degli algoritmi necessari alla loro gestione è O(n). Esistono altre strutture dati, come ad esempio gli alberi, che possono offrire prestazioni superiori (O(log n)), ma a costo di una più difficile gestione. Particolari liste che trovano un utilizzo più frequente sono le PILE e le CODE, le quali verranno esposte in seguito più dettagliatamente. Una lista è una sequenza di elementi, aventi le seguenti caratteristiche: - Tutti gli elementi sono omogenei, cioè appartengono allo stesso tipo. - Ciascun elemento della lista è contraddistinto da una posizione. In base alla posizione è possibile individuare relazioni di precedenza e di successione tra gli elementi. Particolari elementi sono quello di testa che occupa la prima posizione e quello di oda che occupa l’ultima posizione. È ovvio che il primo elemento della lista non ha predecessore, e l’ultimo elemento non ha successore. Una lista viene solitamente rappresentata come sequenza degli elementi che la compongono, disposti in accordo alla loro posizione. Sia ai un generico elemento e sia i la sua posizione nella lista. Una lista composta da n elementi dello stesso tipo di ai, può essere rappresentata da a1, a2, …, an. Si chiama lunghezza della lista il numero n di elementi che la compongono. Una lista è individuata dall’elemento in prima posizione. Una lista vuota non presenta alcun elemento. Di seguito viene riportato un esempio di lista circolare: questa struttura data è caratterizzata dallo scorrimento di due indicatori di posizione (inizio e fine), che rispettivamente indicano il primo e l’ultimo elemento della lista. La lista circolare segue un particolare algoritmo che permette di risparmiare del tempo prezioso per quanto riguarda l’ordinamento degli elementi. #include <stdio.h> #include <stdlib.h> #include <conio.h> #define MAX 10 typedef enum {ERROR,OK} boolean; typedef struct _lista { int num; /* Numero di elementi nella lista */ int inizio; /* Posizione elemento di testa */ int fine; /* Posizione elemento di coda */ int data[MAX]; } lista; boolean inserisci (lista **l); boolean estrai (lista **l, int *elem); boolean cancella (lista **l); 5 De Mauro, Fazio - ADT boolean inizializza (lista **l); void main () { int n, elemento; lista *l=NULL; do { clrscr(); printf ("\n\nGESTIONE DI UNA LISTA\n\n"); printf ("1-Inserimento\n"); printf ("2-Estrazione elemento\n"); printf ("3-Cancella lista\n"); printf ("4-Inizializza lista\n"); printf ("5-Controlla lista\n"); printf ("6-Esci\n\n"); printf ("Inserisci il comando: "); scanf ("%d" ,&n); switch (n) { case 1 : if (l!=NULL) if (inserisci(&l)) { printf("\n\nElemento inserito correttamente\n"); getche(); } else { printf("\n\nLa lista e’ piena!!\n"); getche(); } else { printf("\n\nInizializzare la lista!!\n"); getche(); } break; case 2 : if (l!=NULL) { if (estrai(&l , &elemento)) { printf("\n\nIl numero estratto e’: %d ", elemento); getche(); } else { printf ("\n\nLa lista e’ vuota!!\n\n"); getche(); } } else printf("\nInizializzare la lista!!\n\n"); 6 De Mauro, Fazio - ADT break; case 3 : if (cancella(&l)) { printf("\nLista cancellata correttamente.\n\n"); getche(); } else { printf("\nImpossibile cancellare la lista.\n\n"); getche(); } break; case 4 : if (!inizializza(&l)) { printf("\nNon e’ possibile creare una lista: controllare se esiste già\n"); getche(); } else { printf("\n\nLista creata correttamente\n"); getche(); } break; case 5 : if (l!=NULL) { printf("\n\nLa lista contiene %d elementi\n\n", (*l).num); getche(); } else { printf ("\n\nDevi inizializzare la lista\n\n"); getche(); } break; case 6 : break; default: printf("\n\nComando errato!!!"); getche(); break; } } while (n!=6); } boolean inserisci (lista **l) { int val; 7 De Mauro, Fazio - ADT if ((**l).num==MAX&&(**l).inizio==(**l).fine) /*lista piena*/ return ERROR; printf ("\nInserisci l'elemento: "); scanf ("%d", &val); if ((**l).num==0&&(**l).inizio==(**l).fine) /* lista vuota */ { (**l).data[0]=val; (**l).inizio=0; (**l).fine=1; (**l).num++; } else { (**l).data[(**l).fine]=val; (**l).fine++; (**l).num++; } (**l).fine=(**l).fine % MAX; return OK; } boolean estrai (lista **l, int *elem) { if ((**l).num==0&&(**l).inizio==(**l).fine) return ERROR; else *elem=(**l).data[(**l).inizio]; (**l).inizio++; (**l).num--; (**l).inizio= (**l).inizio % MAX; return OK; } boolean cancella (lista **l) { if (*l==NULL) return ERROR; else { free (*l); *l=NULL; } return OK; } boolean inizializza (lista **l) { if (*l!=NULL) return ERROR; else { *l=(lista*)malloc(sizeof(lista)); (**l).inizio=0; (**l).fine=0; (**l).num=0; return OK; } 8 De Mauro, Fazio - ADT } Stack Lo stack è una particolare lista in cui l’inserimento e la cancellazione è consentita solo ad una estremità. Questa gestione è anche chiamata LIFO (Last In First Out), in quanto l’ultimo elemento inserito è il primo ad essere estratto. L’estremità della lista in cui è consentito l’inserimento e l’estrazione è chiamata top o cima. Sia data una pila composta dagli elementi a1, a2, a3, …., an e si supponga che la cima sia rappresentata dalla posizione del primo elemento a1. L’inserimento di un elemento x produce la lista x, a1, a2, a3, …, an. la nuova cima dello STACK diviene la posizione di x. La cancellazione della cima ripristina la pila iniziale a1, a2, a3, …, an . Qui di seguito viene riportato un esempio a quanto detto in precedenza: #include <stdio.h> #include <stdlib.h> #include <conio.h> #define MAX 10 typedef enum {ERROR,OK} boolean; typedef struct _stack { int num; stack */ int vett[MAX]; } stack; boolean boolean boolean boolean /* Numero di elementi nello crea_pila (stack **p); distruggi_pila (stack *p); push (stack *p); pop (stack *p, int &estratto); void main () { int n, estratto; stack *p=NULL; do { clrscr(); printf ("\n\nMen— di gestione stack.\n\n"); printf ("1-Inserimento\n"); printf ("2-Estrazione elemento\n"); printf ("3-Cancellazione stack\n"); printf ("4-Inizializza\n"); printf ("5-Controlla stack\n"); printf ("6-Esci\n\n"); printf ("Inserisci il comando: "); scanf ("%d" ,&n); switch (n) { 9 De Mauro, Fazio - ADT case 1 : if (p!=NULL) if ((*p).num<MAX) if (push(p)) { printf("\n\nElemento inserito correttamente\n"); getche(); } else { printf("\n\nImpossibile inserire l'elemento\n"); getche(); } else { printf("\n\nLa pila e’ piena!!\n"); getche(); } else { printf("\n\n Inizializzare la pila!!\n"); getche(); } break; case 2 : if (pop(p , estratto)) { printf("\n\nIl numero estratto e’: %d ", estratto); getche(); } else { printf ("\n\nImpossibile estrarre il numero.\n\n"); getche(); } break; case 3 : if (distruggi_pila(p)) { printf("\nStack cancellato correttamente.\n\n"); getche(); } else { printf("\nImpossibile cancellare lo stack.\n\n"); getche(); } break; case 4 : if (!crea_pila(&p)) { 10 De Mauro, Fazio - ADT printf("\nNon e’ possibile creare un nuovo stack: gi…\n"); controllare se esiste getche(); } else { printf("\n\nStack creato correttamente\n"); getche(); } break; case 5 : if (p!=NULL) { printf("\n\nLo stack contiene %d elementi\n\n", (*p).num); getche(); } else { printf ("\n\nDevi inizializzare lo stack\n\n"); getche(); } break; case 6 : break; default: printf("\n\nComando errato!!!"); getche(); break; } } while (n!=6); } boolean crea_pila (stack**p) { if (*p!=NULL) return ERROR; *p=(stack*)malloc(sizeof(stack)); if(*p==NULL) return ERROR; (**p).num=0; return OK; } boolean distruggi_pila (stack *p) { if (p==NULL) return ERROR; free (p); p=NULL; return OK; } 11 De Mauro, Fazio - ADT boolean push (stack *p) { int val; if (p==NULL) return ERROR; else { printf("\nInserisci l'elemento : "); scanf("%d", &val); (*p).num++; (*p).vett[(*p).num-1]=val; return OK; } } boolean pop (stack *p, int &estratto) { if (p==NULL) return ERROR; if ((*p).num==0) return ERROR; estratto=(*p).vett[(*p).num-1]; (*p).num--; return OK; } 12 De Mauro, Fazio - ADT Coda La coda è una particolare lista in cui l’inserimento è consentito solo ad un estremo, detto rear, la cancellazione è consentita solo all’altro estremo, detto front. Questa gestione è anche chiamata FIFO (First In First Out), in quanto il primo elemento inserito è il primo ad essere estratto. Sia data una coda composta dagli elementi a1, a2, a3, …, an e si supponga che il front sia rappresentato dalla posizione del primo elemento a1 mentre il rear dalla posizione dell’ultimo elemento an. L’inserimento di un elemento x produce la lista a1, a2, a3, …, an, x. Il nuovo rear diviene la posizione di x. La cancellazione dell’elemento di posizione front, produce la lista a2, a3, …, an, x. Il nuovo front è relativo all’elemento a2. Qui di seguito viene riportato un esempio a quanto detto in precedenza: #include <stdio.h> #include <stdlib.h> #include <conio.h> #define MAX 10 typedef enum {ERROR,OK} boolean; typedef struct _coda { int num; int vett[MAX]; } coda; boolean boolean boolean boolean boolean inizializza (coda **c); cancella ( coda **c); inserisci (coda **c); controlla (coda *c ,int *num_ele); estrai (coda **c, int *estratto); void main (void) { int n, num_elementi, elemento; coda *c=NULL; while (n!=6) { getche (); clrscr(); printf ("\nG E S T I O N E D I U N A C O D A\n\n"); printf ("1-Inizializza coda\n"); printf ("2-Cancella coda\n"); printf ("3-Inserisci elemento\n"); printf ("4-Estrai elemento\n"); printf ("5-Controlla coda\n"); printf ("6-Esci\n\n"); printf ("Inserisci il comando: "); scanf ("%d", &n); switch (n) { case 1: if (!inizializza(&c)) printf ("\nErrore durante l'inizializzazione della coda!!\nControllare se esiste gi….\n\n"); 13 De Mauro, Fazio - ADT else printf ("\nCoda creata con successo!!\n\n"); break; case 2: if (!cancella(&c)) printf ("\nImpossibile cancellare la coda. Controllare se esiste!!\n\n"); else printf ("\nCoda cancellata con successo\n\n"); break; case 3: if (c!=NULL) { if (!inserisci(&c)) printf ("\nLa coda e’ piena!!\n\n"); else printf ("\nElemento inserito correttamente\n\n"); } else printf ("\nDevi inizializzare prima la coda!!\n\n"); break; case 4: if (c!=NULL) { if (!estrai( &c , &elemento)) printf ("\nNon ci sono elementi dentro la coda!!\n\n"); else printf ("\nL'elemento estratto e’ %d \n\n", elemento); } else printf ("\nDevi inizializzare la coda!!\n\n"); break; case 5: if (!controlla (c , &num_elementi)) printf ("\nDevi inizializzare la coda!!\n\n"); else printf ("\nNella coda ci sono %d elementi.\n\n", num_elementi); break; case 6: default: printf ("\n\nComando errato!!\n"); break; } } } boolean inizializza ( coda **c) { if (*c!=NULL) return ERROR; 14 De Mauro, Fazio - ADT else { *c=(coda*)malloc(sizeof(coda)); (**c).num=0; } return OK; } boolean cancella ( coda **c) { if (*c==NULL) return ERROR; else free (*c); *c=NULL; return OK; } boolean inserisci (coda **c) { int elemento; if ((**c).num<MAX) { printf ("\nInserisci l'elemento da inserire in coda: scanf ("%d", &elemento); (**c).num++; (**c).vett[(**c).num-1]=elemento; return OK; } else return ERROR; } "); boolean controlla (coda *c ,int *num_ele) { if (c==NULL) return ERROR; else *num_ele=(*c).num; return OK; } boolean estrai (coda **c, int *estratto) { int i; if ((** c).num==0) return ERROR; else { *estratto=(**c).vett[0]; for (i=1 ; i<(**c).num ; i++) (**c).vett[i-1]=(**c).vett[i]; (**c).num--; } return OK; } 15 De Mauro, Fazio - ADT Link utili [1] [2] [3] [4] [5] [6] [7] NIST – National Institute of Standards and Technology, <http://www.nist.gov/dads/HTML/abstractDataType> Università di Catania – Dipartimento di Informatica, Cavalieri, S. (a cura di), <http://www.diit.unict.it/users/scava/dispense/FdI/ADT.pdf e ADT-Pila.pdf> Università di Torino – Dipartimento di informatica, <http://www.di.unito.it/~deligu/didattica/algo/ADT.pdf> Università di Pisa – Dipartimento di Informatica, <http://www.cli.di.unipi.it/~semini/LI2D00/lez7.html> McGill University – Department of Computer Science, D’urso Alberto (a cura di), <http://www.cs.mcgill.ca/~cs251/OldCourses/1997/topic1/> Politecnico di Torino – Dipartimento di Informatica, Corno, F., Sonza Reorda, M. (a cura di), <http://www.cad.polito.it/~corno/apa/lucidi/fifo-lifo.pdf> Università di Trento, http://www2.inf.unitn.it/~marchese/c++/07_intro_classi.PDF> 16