Abstract Data Type - Politecnico di Torino

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