Fondamenti di Informatica 2 Ingegneria Informatica Docente

Fondamenti di Informatica 2
Ingegneria Informatica
Docente: Giovanni Macchia
a.a. 2002-2003
Strutture Dati
Una struttura dati già studiata è l’array.
L’array ha comunque delle grosse limitazioni:
• l’ampiezza dell’array deve essere conosciuto
durante la compilazione
• i dati dell’array sono separati dalla stessa distanza
nella memoria del computer : l’inserimento di un
elemento all’interno dell’array richiede lo
spostamento di altri dati
Per ovviare a queste limitazioni, si ricorre alle
strutture dati dinamiche, ovvero a strutture le cui
dimensioni possono modificarsi durante
l’esecuzione del programma.
Strutture Dati: Linked List
Una lista concatenata (linked list) è una sequenza
lineare di oggetti detti nodi contenenti dati e
puntatori ad oggetti della stessa classe dei nodi.
L’accesso ad una linked list avviene tramite il
puntatore al suo primo nodo. L’accesso agli altri
nodi della lista avviene tramite il link al nodo
successivo.
Il puntatore al nodo successivo dell’ultimo elemento
della lista viene posto uguale a NULL per
convenzione.
Strutture Dati: Linked List
Es:
class IntNode {
public:
IntNode ( );
IntNode(int i, IntNode * in = NULL) {.. } ;
int info;
IntNode *next;
};
IntNode *p = new IntNode(10);
p->next = new IntNode(8);
p->next->next = new IntNode(6);
Strutture Dati: Linked List
a) IntNode *p = new IntNode(10);
b) p->next = new IntNode(8);
c) p->next->next = new IntNode(6);
a)
b)
c)
10
NULL
10
10
8
NULL
8
6
NULL
Strutture Dati: Linked List
Una gestione molto elegante delle liste concatenate
si ottiene con una implementazione ad oggetti.
Si considerano due classi: una classe Nodo e una
classe Lista.
Gli attributi della classe Nodo sono :
• i dati del nodo
• il puntatore al nodo successivo
Il metodo della classe Nodo è il costruttore. Quando
un oggetto di classe Nodo viene creato, vengono
inizializzati gli attributi del nodo.
Strutture Dati: Linked List
Gli attributi della classe Lista sono due:
• il puntatore al primo elemento della lista
• il puntatore all’ultimo elemento della lista
Quando un oggetto di classe Lista viene creato, i
puntatori al primo e all’ultimo elemento vengono
inizializzati a NULL.
Strutture Dati: Linked List
I metodi della classe Lista coincidono con le
operazioni che possono essere eseguite sulla lista.
Le operazioni su una lista concatenata sono le
seguenti:
• addtofirst : aggiungi un nodo in cima alla lista
• addtolast : aggiungi un nodo alla fine della
lista
• deletefirst : rimuovi il primo nodo della lista
• deletelast : rimuovi l’ultimo nodo della lista
• isEmpty : verifica se la lista è vuota
• deleteNode(el) : rimuove un nodo con elemento
el dalla lista
I metodi della classe Lista usano i metodi della
classe Nodo.
Strutture Dati: Linked List
Es:
class IntNode {
public:
IntNode *next;
IntNode(int i, IntNode *ptr = NULL) {info =i; next =ptr; } ;
int info;};
class IntList {
public:
IntList( ) {first = last = NULL};
~IntList( );
bool isEmpty( ) {return (first == NULL) };
void addtofirst (int );
void addtolast (int );
bool deletefirst( int &);
bool deletelast( int &);
void deleteNode(int &);
private:
IntNode *first, *last;};
Strutture Dati: Linked List
L’algoritmo per aggiungere un elemento el in cima
alla lista (addtofirst)è il seguente:
CREA un nuovo nodo N (contente el ) con il primo nodo
della lista come nodo successivo ad N;
PONI N come primo elemento della lista ;
SE l’ultimo elemento della lista non esiste
ALLORA
PONI il primo elemento della lista come ultimo elemento
della lista;
Strutture Dati: Linked List
L’algoritmo per aggiungere un elemento el alla fine
della lista (addtolast)è il seguente:
SE la lista non è vuota
ALLORA
CREA un nuovo nodo N (contente el ) con l’ultimo nodo
della lista come nodo predecessore di N;
PONI N come ultimo nodo della lista;
ALTRIMENTI
CREA un nuovo nodo N (contente el ) che non ha nodi
successivi;
PONI N come primo ed ultimo nodo della lista;
Strutture Dati: Linked List
void addtofirst (int el)
{ first = new IntNode (el, first);
if (last == NULL) last = first; }
void addtolast (int el)
{ if (last != NULL)
// se la lista non è vuota
{ last->next = new IntNode (el);
last = last->next; }
else
first = last = new IntNode(el);
}
Strutture Dati: Linked List
L’algoritmo per rimuovere il primo elemento della
lista (deletefirst) è il seguente:
SE la lista non è vuota
ALLORA
SE la lista ha un solo nodo
ALLORA
ELIMINA il nodo;
ALTRIMENTI
PONI il nodo successivo al primo come primo nodo
della lista;
Strutture Dati: Linked List
bool deletefirst (int &el)
{ if (isEmpty()) return false;
else
{
IntNode *tmp = first;
if (first == last) { delete first; first = last = NULL; }
else
first = first->next;
el = tmp->info;
delete tmp;
return true;
}
}
Strutture Dati: Linked List
L’algoritmo per rimuovere l’ultimo nodo della lista
(deletelast) è il seguente:
SE la lista non è vuota
ALLORA
SE la lista ha un solo nodo
ALLORA
ELIMINA il nodo;
ALTRIMENTI
TROVA il nodo N immediatamente precedente
all’ultimo nodo della lista;
PONI N come ultimo nodo della lista;
Strutture Dati: Linked List
bool deletelast (int &el)
{ if (isEmpty()) return false;
else
{
IntNode *tmp = last;
if (first == last) { delete first; first = last = NULL; }
else
{ IntNode *curr =first;
while (curr->next != last)
curr = curr->next;
last = curr;
curr->next = NULL; };
el = tmp->info;
delete tmp;
return true;
}
}
Strutture Dati: Linked List
L’algoritmo per rimuovere un elemento el della lista
(deleteNode) è il seguente:
SE la lista non è vuota
ALLORA
SE ( la lista ha un solo nodo E l’elemento del nodo è uguale a el)
ALLORA ELIMINA il nodo
ALTRIMENTI
SE il primo nodo N1 ha elemento uguale a el
ALLORA
PONI il secondo nodo come primo nodo della lista;
ELIMINA N1;
ALTRIMENTI
CERCA il predecessore P e il successore S del nodo N
con elemento el;
SE P ed S esistono ALLORA
PONI S come successore di P;
ELIMINA il nodo N;
Strutture Dati: Linked List
void deleteNode (int &el)
{ if (!isEmpty())
{ if (first ==last && el == first->info)
{ delete first; first = last = NULL; }
else if (el == first->info)
{ IntNode *tmp =first;
first = first->next;
delete tmp; }
else
{IntNode *pred, *succ;
for(pred=first,succ=first->next;
succ !=NULL && !(succ->info =el);
pred =pred->next,succ=succ->next);
if (succ != NULL) { pred->next=succ->next;
if (succ == last) last = pred; delete succ;}
}
}
}
Strutture Dati: Linked List
E’ possibile generalizzare le liste concatenate
tramite i template. In questo caso, il template della
classe Node è:
template <class TIPONODO>
class Node {
public:
Node<TIPONODO> *next;
Node (TIPONODO, Node<TIPONODO> *) ;
TIPONODO info;};
template <class TIPONODO>
Node<TIPONODO>::Node(TIPONODO i, Node<TIPONODO>
*ptr = NULL)
:info(i) {next = ptr; }
Strutture Dati: Linked List
Il template della classe List è:
template <class TIPONODO>
class List {
public:
List( );
~List( );
bool isEmpty( );
void addtofirst (TIPONODO );
void addtolast (TIPONODO );
bool deletefirst( TIPONODO &);
bool deletelast( TIPONODO &);
void deleteNode(TIPONODO &);
private:
Node<TIPONODO> *first, *last;};
Strutture Dati: Linked List
Anche i metodi della classe vengono ridefiniti per
renderli dei template:
template <class TIPONODO>
void List<TIPONODO>:: addtofirst (TIPONODO el)
{ first = new Node<TIPONODO> (el, first);
if (last == NULL) last = first; }
template <class TIPONODO>
void List<TIPONODO>:: addtolast (TIPONODO el)
{ if (last != NULL)
// se la lista non è vuota
{ last->next = new Node<TIPONODO>(el);
last = last->next; }
else
first = last = new Node<TIPONODO>(el);
}
Strutture Dati: Linked List
template <class TIPONODO>
bool List<TIPONODO>:: deletefirst (TIPONODO &el)
{ if (isEmpty()) return false;
else
{
Node<TIPONODO> *tmp = first;
if (first == last) { delete first; first = last = NULL; }
else
first = first->next;
el = tmp->info;
delete tmp;
return true;
}
}
Strutture Dati: Linked List
template <class TIPONODO>
bool List<TIPONODO>::deletelast (TIPONODO &el)
{ if (isEmpty()) return false;
else
{
Node<TIPONODO> *tmp = last;
if (first == last) { delete first; first = last = NULL; }
else
{ Node<TIPONODO> *curr =first;
while (curr->next != last)
curr = curr->next;
last = curr;
curr->next = NULL; };
el = tmp->info;
delete tmp;
return true;
}
}
Strutture Dati: Linked List
addtofirst(1)
addtofirst(5)
1
8
addtofirst(8)
5
1
4
1
5
addtolast(4)
deletelast
8
1
1
4
6
deletefirst
1
4
addtofirst(6)
8
1
Strutture Dati: Stack
Una pila (stack) è una struttura lineare di oggetti che
possono essere aggiunti o rimossi soltanto dalla sua
cima (top). Pertanto, l’ultimo oggetto posto sulla
cima dello stack è il primo ad essere rimosso.
Per questa ragione, uno stack è chiamato una
struttura LIFO (last-in/first-out).Le operazioni che
possono essere eseguite su uno stack sono:
• push(el) : mette el sulla cima della pila
• pop( ) : prende l’elemento dalla cima della pila
•clear( ) : azzera lo stack
•isEmpty( ) : verifica se lo stack è vuoto
Strutture Dati: Stack
STACK
pop( )
push( )
Strutture Dati: Stack
Uno Stack si ottiene considerando una classe Nodo, Lista e Stack che
usa i metodi forniti dalla classe Lista.
Es:
#include “IntList.h”
class IntStack {
public:
IntStack( ) ;
~IntStack( );
bool isEmpty( );
void push ( int &el) { IntList. addtofirst (int el);};
bool pop (int &el) {return IntList. deletefirst (int el); };
void clear();
};
Strutture Dati: Stack
Così come per le liste concatenate, è possibile
generalizzare lo stack tramite i template. In questo
caso, il template della classe Stack è:
template <class TIPONODO>
class Stack {
public:
Stack( );
~Stack( );
bool isEmpty( );
void push (TIPONODO & );
bool pop (TIPONODO &);
void clear();
};
Strutture Dati: Stack
template <class TIPONODO>
void Stack<TIPONODO>::push(TIPONODO &el)
{List<TIPONODO>.addfirst(TIPONODO el); };
template <class TIPONODO>
bool Stack<TIPONODO>::pop(TIPONODO &el)
{List<TIPONODO>.deletefirst(TIPONODO el); };
Strutture Dati: Stack
L’implementazione dello stack in maniera
tradizionale si ottiene tramite delle operazioni su
puntatori. Per esempio, la funzione push per inserire
un elemento nello stack dove il top della pila è
riferito al puntatore lis:
tmpp ->next = lis;
lis = tmpp;
Strutture Dati: Stack
tmpp
lis
tmpp ->next = lis
tmpp
lis
lis = tmpp
tmpp
lis
Strutture Dati: Queue
Una coda (queue) è una struttura lineare di oggetti
in cui si può inserire un oggetto solo alla fine della
coda ed eliminare un oggetto solo dalla cima della
coda.
Per questa ragione, una coda è chiamata una
struttura FIFO (first-in/first-out).Le operazioni che
possono essere eseguite su una coda sono:
• enqueue(el) : mette el sul fondo della coda
• dequeue( ) : prende il primo elemento dalla coda
•clear( ) : azzera la coda
•isEmpty( ) : verifica se la coda è vuota
Strutture Dati: Queue
enqueue( )
dequeue( )
QUEUE
Strutture Dati: Queue
Una Queue si ottiene considerando una classe Nodo, Lista e Queue
che usa i metodi forniti dalla classe Lista.
Es:
#include “IntList.h”
class IntQueue {
public:
IntQueue( ) ;
~IntQueue( );
bool isEmpty( );
void enqueue ( int &el) { IntList. addtolast (int el);};
bool dequeue (int &el) {return IntList. deletefirst (int el); };
void clear();
};
Strutture Dati: Tree
Un albero (tree) è una struttura non lineare di archi e
nodi. Questi ultimi contengono due o più membri di
link ad altri nodi. Un albero con esattamente due
link si chiama albero binario.
Il primo nodo della struttura ad albero si chiama
radice (root) ed ha la proprietà di non discendere da
altri nodi.
Un nodo dal quale discendono altri nodi si chiama
nodo padre. I nodi che discendono dal nodo padre si
chiamano nodi figlio.
Un nodo senza figli è chiamato nodo foglia (leaf)
Un nodo di un albero binario può avere da 0 a 2 figli
Un nodo di un albero n-ario può avere da 0 a n figli.
Strutture Dati: Tree
Un albero è definito in maniera ricorsiva in base alle
seguenti regole:
1. un insieme vuoto di nodi è un albero vuoto
2. se t1, t2,…,tk sono alberi disgiunti, allora la
struttura la cui radice ha come figli le radici di t1,t2,
…, tk è anche un albero
3. Solo strutture generate dalle regole 1 e 2 sono
alberi
Strutture Dati: Tree
Ogni nodo è raggiungibile dalla radice attraverso
una sequenza unica di archi, chiamata path.
Il numero di archi in un path è chiamata lunghezza
del path.
Si definisce livello di un nodo la lunghezza del path
dalla radice al nodo stesso aumentata di uno.
La altezza di un albero è la lunghezza massima di
un nodo nell’albero.
Strutture Dati: Tree
La figura rappresenta la struttura di un albero binario dove ogni nodo
ha 2 figli.
LIVELLO 1
LIVELLO 2
LIVELLO 3
.
.
.
.
.
.
.
.
.
.
...........................................................
LIVELLO n
Strutture Dati: Tree
Nella implementazione, faremo riferimento ad un
particolare albero binario, detto albero binario di
ricerca (search binary tree). Un albero binario di
ricerca ha la proprietà che il valore di ogni nodo è:
• maggiore del valore dei nodi del suo sottoalbero
sinistro
• minore del valore dei nodi del suo sottoalbero
destro
40
33
18
70
34
50
77
Strutture Dati: Tree
In un albero binario di ricerca dove ogni nodo ha 2
figli, il numero di nodi presenti al livello k-mo è
pari a 2k-1. Il numero di nodi totali presenti in un
albero di ricerca ben bilanciato è pertanto pari a
n = 20 + 21 + 22 + ... 2k-1= 2k - 1
Il numero di livelli di un albero ben bilanciato
contenente n nodi è pertanto
k=log2(n+1) .
Per effettuare una ricerca su 1024 elementi
occorrono pertanto 10 confronti, mentre per
effettuare una ricerca su 1048576 elementi (220)
occorrono 20 confronti
Strutture Dati: Tree
I metodi che devono essere implementati come
pubblici sono relativi all’inserimento di un nodo e
all’estrazione di un nodo.
Per estrarre un nodo da un albero, si usano le
operazioni di visita (tree traversal) ovvero si
procede in un certo ordine per l’estrazione di un
elemento.
I più comuni tipi di visita sono:
visita in preordine (preorder tree traversal)
visita in postordine (postorder tree traversal)
visita in ordine simmetrico (inorder tree traversal)
Strutture Dati: Tree
class IntTreeNode {
friend class IntTree;
public:
IntTreeNode(const int &el )
: leftPtr(NULL), data (el), rightPtr(NULL) { } ;
int getData ( ) const { return data; } ;
private:
IntTreeNode *leftPtr;
IntTreeNode *rightPtr;
int data;
};
Strutture Dati: Tree
class IntTree {
public:
IntTree( ) { rootPtr = NULL;};
~IntTree( );
void insertNode ( const int &);
//inserimento di un nodo
void preordertraversal( ) const; // visita in preordine
void inordertraversal( ) const; // visita in ordine simmetrico
void postordertraversal( ) const; //visita in postordine
private:
IntTreeNode *rootPtr;
void insertNodeHelper (IntTreeNode **, const int &);
void preorderHelper (IntTreeNode * ) const;
void inorderHelper (IntTreeNode * ) const;
void postorderHelper (IntTreeNode * ) const;
void visit (IntTreeNode * ) const;
};
Strutture Dati: Tree
L’inserimento di un nodo avviene con il seguente
algoritmo:
void IntTree::insertNode (const int &el )
{insertNodeHelper(&rootPtr,el); };
void IntTree::insertNodeHelper (IntTreeNode **p, const int &el )
{ if (*p == NULL) {
Se l’albero è vuoto creo la radice
*p = new IntTreeNode(el);
}
else
if (el < (*p)->data ) insertNodeHelper (&((*p)->leftPtr), el);
else
if (el > (*p)->data ) insertNodeHelper (&((*p)->rightPtr), el);
};
Strutture Dati: Tree
La visita in preordine avviene con il seguente
algoritmo: se l’albero non è vuoto, visita in
preordine il sottoalbero sinistro e poi visita in
preordine il sottoalbero destro.
void IntTree::preordertraversal ( ) const
{preorderHelper(rootPtr); };
void IntTree::preorderHelper (IntTreeNode *p) const
{
if (p != NULL) {
visit(p);
preorderHelper (p->leftPtr);
preorderHelper (p->rightPtr);
};
};
Strutture Dati: Tree
Visita in preordine
1o sottoalbero
ad essere visitato
2o sottoalbero
ad essere visitato
18
13
10
25
14
22
I nodi sono visitati nel seguente ordine:
18
13 10 14
25 22 27
27
Strutture Dati: Tree
La visita in postordine avviene con il seguente
algoritmo: se l’albero non è vuoto, visita in
postordine il sottoalbero sinistro e poi visita in
postordine il sottoalbero destro.
void IntTree::postordertraversal ( ) const
{postorderHelper(rootPtr); };
void IntTree::postorderHelper (IntTreeNode *p) const
{
if (p != NULL) {
postorderHelper (p->leftPtr);
postorderHelper (p->rightPtr);
visit(p); };
};
Strutture Dati: Tree
Visita in postordine
1o sottoalbero
ad essere visitato
2o sottoalbero
ad essere visitato
18
13
10
25
14
22
I nodi sono visitati nel seguente ordine:
10 14 13
22 27 25
18
27
Strutture Dati: Tree
La visita in ordine simmetrico avviene con il
seguente algoritmo: se l’albero non è vuoto, visita in
ordine simmetrico il sottoalbero sinistro e poi visita
in ordine simmetrico il sottoalbero destro.
void IntTree::inordertraversal ( ) const
{inorderHelper(rootPtr); };
void IntTree::inorderHelper (IntTreeNode *p) const
{
if (p != NULL) {
inorderHelper (p->leftPtr);
visit(p);
inorderHelper (p->rightPtr);
};
};
Strutture Dati: Tree
Visita in ordine simmetrico
1o sottoalbero
ad essere visitato
2o sottoalbero
ad essere visitato
18
13
10
25
14
22
I nodi sono visitati nel seguente ordine:
10 13 14
18
22 25 27
27