Fondamenti di Informatica II
Ingegneria Informatica (A-I)
Prof. M.T. PAZIENZA
a.a. 2003-2004 – 3° ciclo
Liste
Una lista è una struttura dati ricorsiva (formata da elementi
dello stesso tipo e collegati insieme) la cui lunghezza può
variare dinamicamente.
I suoi elementi sono variabili dinamiche che vengono creati
e/o distrutti a tempo di esecuzione producendo una
struttura dati che cresce o diminuisce a seconda delle
esigenze del programma in esecuzione.
E’ possibile implementare liste anche tramite array, ma ciò
può avvenire solo quando si conoscono esattamente le
dimensioni della lista.
Liste
Ogni elemento di una lista è definito come una struttura
costituita da uno o più campi dati e da un campo
puntatore contenente l’indirizzo dell’elemento
successivo.
struct elem
{ int info;
elem * succ;
};
Strutture concatenate
Una struttura é detta concatenata quando é costituita, oltre
che dai suoi normali membri, anche da uno o più membri
aggiuntivi, dichiarati come puntatori alla struttura stessa.
struct rec
{int info;
rec*next;
};
La definizione di una struttura concatenata é di solito
accompagnata da un certo numero di funzioni, che hanno il
compito di gestirla, cioè eseguire le operazioni di
inserimento, di eliminazione e di ricerca di oggetti.
Liste
Ogni lista è definita da una variabile puntatore che
punta al primo elemento della lista.
Nel caso di assenza di elementi (lista vuota) tale
variabile puntatore assume valore NULL.
In una lista il campo puntatore dell’ultimo elemento
assume sempre valore NULL.
Liste
lis-> info
lis
Puntatore alla
(testa di)
lista
Dati
lis->next
Dati
Puntatore al
sucessivo
Dati
0
Puntatore
all’ultimo
elemento
struct rec
{
int info;
rec* next;
};
Tipi di Lista
Lista circolare (l’ultimo puntatore non ha valore NULL,
ma punta al primo nodo)
Lista doppia (può essere visitata nei due sensi ->
presenza di due puntatori per nodo)
Lista doppia circolare (il puntatore all’elemento
successivo dell’ultimo nodo punta al primo elemento
della lista e il puntatore all’elemento precedente del
primo punta all’ultimo elemento della lista)
Allocazione dinamica di liste
L’allocazione dinamica della memoria si presta alla
gestione di liste di oggetti, quando il loro numero non é
definito a priori.
Queste liste possono aumentare e diminuire di dimensioni
dinamicamente in base al flusso del programma, e quindi
devono essere gestite in un modo più efficiente
dell’allocazione di memoria permanente sotto forma di
array
Liste concatenate
Una lista concatenata (linked list) é un insieme di oggetti,
caratterizzati dal fatto di essere istanze di una struttura
concatenata.
In ogni oggetto, i membri puntatori alla struttura contengono
l’indirizzo di altri oggetti della lista, creando così un “legame” fra
gli oggetti e rendendo la stessa lista “percorribile”, anche se gli
oggetti non sono allocati consecutivamente in memoria.
Se la struttura possiede un solo membro puntatore a se stessa, la
lista é detta single-linked, se ne possiede due, é detta double-linked.
Inserimento in testa
rec* tempp= new rec;
tempp->info = dato;
tempp
dato
lis
tempp
0
dato
tempp->next = lis;
lis
lis = tempp;
tempp
lis
0
dato
0
Estrazione dalla testa
tempp
rec* tempp= lis;
lis
tempp
lis = lis->next;
lis
tempp
tempp->next = NULL;
lis
0
Operazioni su liste
•
•
•
•
Creazione della lista (vuota e successivi inserimenti)
Lettura di una lista
Stampa di una lista
Cancellazione di una lista
• Inserimento in lista
• Estrazione da lista
Creazione della lista
Per creare una lista, basta definirla, ovvero è sufficiente
creare il modo di riferirsi ad essa. L’unica cosa che esiste
sempre della lista è la sua testa (o radice) ossia il
puntatore al suo primo elemento.
Questa è l’unica componente allocata staticamente ed è
inizializzata a NULL poiché all’inizio(creazione della lista)
non punta a niente in quanto non ci sono elementi.
Es.:
lis NULL
rec* lis=NULL;
Puntatore
alla lista
Creazione di un nuovo nodo
La creazione di un nuovo nodo (in qualunque fase
dell’esistenza di una lista) avviene creando una
nuova istanza della struttura tramite allocazione
dinamica, utilizzando di solito un puntatore
d’appoggio (tempp)
Es.:
rec* tempp = new rec;
tempp
info
next
Assegnazione di valori ai campi dati
L’assegnazione di valori ai campi dati si ottiene
dereferenziando il puntatore al nodo e accedendo ai
singoli dati, ovvero utilizzando direttamente
l’operatore ->
Es.:
tempp->info=7;
tempp
7
info next
Lettura di una lista
rec* LeggiListaR() {
int val; cin >> val;
if (val == 0) return NULL;
else {
rec* l = new rec;
l->info = val;
l->next = LeggiListaR();
return l;
}
}
Lettura lista
versione ricorsiva
rec* LeggiListaI() {
int val; cin >> val;
if (val == 0) return NULL;
else {
rec* l = new rec;
l->info = val;
Nodo lista
rec* ll = l; cin >> val;
while (val != 0) {
ll->next = new rec;
ll = ll->next;
ll->info = val; cin >> val;
}
ll->next = NULL;
return l;
Lettura lista
}
versione iterativa
}
Stampa di una lista
void StampaListaR(rec* l)
{
if (l ==NULL) cout << endl;
else {
cout << l->info << ' ';
StampaListaR(l->next);
}
}
Stampa lista
versione ricorsiva
void StampaListaI(rec* l)
{
while (l != NULL) {
cout << l->info << ' ';
l = l->next;
}
cout << endl;
}
Nodo lista
Stampa lista
versione iterativa
Cancellazione di una lista
void CancellaListaR(rec*& l)
{
if (l != NULL) {
CancellaListaR(l->next);
delete l;
l = NULL;
}
}
Cancellazione lista
versione ricorsiva
void CancellaListaI(rec*& l)
{
while (l != NULL) {
rec* ll = l;
l = l->next;
delete ll;
}
l = NULL;
}
Nodo lista
Cancellazione lista
versione iterativa
Esempio
#include <iostream.h>
struct rec {
int info;
rec* next;
};
void main()
{
rec* lis;
lis = LeggiListaR();
StampaListaR(lis);
CancellaListaR(lis);
lis = LeggiListaI();
StampaListaI(lis);
CancellaListaI(lis);
}
Programma
Chiamante
Inserimento in lista
Le operazioni di inserimento di un elemento (ed
analogamente quelle di cancellazione)
possono avvenire secondo diverse modalità,
(ovvero in diverse posizioni della lista)
assumendo di volta in volta caratteristiche
specifiche.
Inserimento di un nuovo elemento
In ogni caso l’inserimento di un nuovo elemento nella lista
prevede sempre i seguenti passi:
1) Creazione di un nuovo nodo (allocazione dinamica)
2) Assegnazione di valori ai campi dati
3) Collegamento del nuovo elemento alla lista esistente
• aggiornamento del campo puntatore del nodo
• aggiornamento dei puntatori della lista
Queste due ultime operazioni caratterizzeranno la tipologia
dell’ inserimento
Creazione di un nuovo nodo
La creazione di un nuovo nodo (in qualunque fase
dell’esistenza di una lista) avviene creando una nuova
istanza della struttura tramite allocazione dinamica,
utilizzando di solito un puntatore d’appoggio (tempp)
Es.:
rec* tempp = new rec;
tempp
info
next
Assegnazione di valori ai campi dati
L’assegnazione di valori ai campi dati si ottiene
dereferenziando il puntatore al nodo e accedendo ai singoli
dati, ovvero utilizzando direttamente l’operatore ->
Es.:
tempp->info=7;
tempp
7
info next
Inserimento in testa
Il caso più semplice è costituito dall’ inserimento in testa,
in quanto si dispone di un riferimento esplicito a questa (il
puntatore alla lista lis).
• Il campo next del nuovo nodo punterà allo stesso valore di
lis
• lis punterà al nuovo nodo
tempp->next=lis;
lis=tempp;
NB. Funziona anche se la lista è vuota!
Inserimento in testa
tempp
tempp
dato
lis
dato
lis 0
0
tempp->next = lis;
tempp
tempp
dato
lis
0
lis
dato 0
0
lis = tempp;
tempp
lis
tempp
dato
0
lis
dato 0
Inserimento in coda
L’inserimento in coda è più complesso, in quanto non
abbiamo un puntatore esplicito all’ultimo elemento, ma
dobbiamo prima scorrere la lista per cercarlo. Supponiamo
di averlo trovato e che sia il puntatore p:
Il campo next del nuovo nodo punterà a NULL (in quanto è
l’ultimo)
Il campo next dell’ex ultimo nodo punterà al nuovo nodo
Es.:
tempp->next=NULL;
p->next=tempp;
La lista vuota va gestita come un caso particolare!
Ricerca dell’ultimo elemento
Per cercare l’ultimo elemento, possiamo scorrere la lista
tramite un puntatore ausiliario p, inizializzato a lis.
Es:
rec* p= lis;
while (p->next != NULL) p=p->next;
Dove è l’errore ??
Ricerca dell’ultimo elemento
La condizione (p->next != NULL) nel caso la lista sia
vuota conduce ad un errore fatale in quanto si sta
dereferenziando un puntatore nullo.
lis
0
lis 0
p 0
p
p->next
p->next = ??
Questo è un tipico errore che si fa nella gestione delle
strutture dinamiche!!!
Ricerca dell’ultimo elemento
La procedura corretta è:
rec* p= lis;
while(p!=NULL && p->next!= NULL) p=p->next;
lis
0
lis 0
p 0
p
p->next
Inserimento in coda
tempp->next = p; (o NULL)
lis
0
lis 0
p 0
p
tempp
dato 0
p->next = tempp;
dato 0
lis = tempp;
lis
lis
p 0
p
tempp
tempp
dato 0
tempp
dato 0
Inserimento in una posizione specifica
L’inserimento in una posizione specifica richiede
preventivamente l’individuazione di tale posizione
all’interno della lista e dipende dalla condizione che si
vuole verificare, per cui dobbiamo prima scorrere la lista
per determinarla.
Vediamo ad esempio come comportarsi per inserire i valori
in ordine crescente.
Inserimento ordinato dei valori
Nel caso di inserimento in ordine crescente, la lista
risultante deve rimanere in ogni momento ordinata.
Pertanto, all’inserimento di un nuovo valore, si dovrà
scorrere la lista fino alla posizione corretta per
l’inserimento (fin quando cioè il campo info dei nodi
esistenti risulta minore del dato da inserire).
Ricerca di un elemento qualsiasi
La condizione più sicura da utilizzare in una ricerca è riferirsi
direttamente al puntatore all’elemento nella condizione di
scorrimento. In tal modo però si sorpassa l’elemento
cercato.
Per questo nella ricerca della posizione di inserimento si
usano di solito due puntatori, p e q, che puntano
rispettivamente all’elemento precedente e al successivo.
Es:
rec* q= lis; rec* p= lis;
while (q!=NULL && q->info<tempp->dato)
{p=q; q=q->next;}
Ricerca di una posizione specifica
rec* q= lis; rec* p= lis;
while (q!=NULL && q->info<tempp->dato) {p=q;
q=q->next;}
tempp
7
lis
2
8
10
0
2
5
6
0
p
q
lis
p
q 0
Casi particolari
rec* q= lis; rec* p= lis;
while (q!=NULL && q->info<tempp->dato) {p=q;
q=q->next;}
tempp
7
lis
8
9
p
q
Se q==lis allora p non contiene
l’elemento precedente
lis 0
p 0
q 0
10
0
Inserimento ordinato dei valori
Quindi nel caso generale:
tempp-> next = q;
p->next = tempp;
ma se q==lis (inserimento in testa)
lis=tempp
Inserimento ordinato dei valori
tempp
tempp->next=q
lis
7
2
8
10
0
8
10
0
p
q
tempp
p->next =tempp
lis
p
q
7
2
Inserimento in lista
void instesta(rec*& lis, int a)
{
rec* p = new rec;
p->info = a;
p->next = lis;
lis = p;
}
Inserimento in testa
void insfondo(rec*& lis, int a)
{
rec* p = lis;
for (rec* q = p; q != NULL; q = q->next)
p = q;
q = new rec;
q->info = a;
q->next = NULL;
if (p != NULL)
p->next = q;
else
lis = q;
}
Inserimento in coda
Inserimento in ordine crescente (info)
void inscresc(rec*& lis, int a)
{
rec* p = lis;
for (rec* q = p; q != NULL && q->info < a; q = q->next)
p = q;
rec* r = new rec;
r->info = a;
r->next = q;
if (q != lis)
p->next = r;
else
lis = r;
}
Inserimento in ordine crescente
Eliminazione di un nodo dalla lista
L’eliminazione di un nodo dalla lista prevede:
• Ricerca del nodo da eliminare (se necessaria)
• Salvataggio del nodo in una variabile ausiliaria (per passo 4)
• Scollegamento del nodo dalla lista (aggiornamento dei puntatori
della lista)
• Distruzione del nodo (deallocazione della memoria)
In ogni caso, bisogna verificare inizialmente che la lista non sia già
vuota!
if (lis != NULL)
Ricerca del nodo da eliminare
Dipende dalle esigenze del programma.
Come per l’inserimento, il caso più semplice è costituito
dall’eliminazione del nodo di testa, in quanto esiste il
puntatore lis a questo elemento.
Negli altri casi, si procede come per l’inserimento
Scollegamento del nodo dalla lista
Individuato il nodo, bisogna evitare che la sua rimozione
spezzi la lista.
lis
0
lis
0
lis
0
In generale, è necessario aggiornare il puntatore next
dell’elemento precedente
Eliminazione del nodo di testa
Bisogna aggiornare il puntatore alla testa lis che dovrà
puntare al nodo successivo a quello da eliminare.
rec* tempp=lis; (salvataggio nodo da eliminare)
lis = tempp->next; (aggiornamento lista)
delete tempp; (distruzione nodo)
Eliminazione del nodo di coda
Bisogna aggiornare il campo next relativo al penultimo nodo,
che ora diventa l’ultimo (e quindi assume valore NULL).
Per la ricerca dell’ultimo elemento si usano due puntatori p e q,
che puntano rispettivamente al penultimo e all’ultimo elemento.
La condizione sarà:
rec* q= lis; rec* p= lis;
while (q!=NULL && q->next!=NULL)
q=q->next;}
{p=q;
Casi particolari: l’elemento da eliminare è l’unico della lista.
Eliminazione del nodo di coda
rec* q= lis; rec* p= lis;
while (q!=NULL && q->next!=NULL) {p=q;
q=q->next;}
lis
0
p
q
Se q==lis allora p non contiene
l’elemento precedente
lis
p
q
0
Eliminazione del nodo di coda
tempp
rec* tempp =q;
lis
0
p
q
tempp
p->next =
tempp->next;
oppure
p->next = NULL;
lis
p
q
0
0
Eliminazione del nodo di coda
rec* tempp = q;
tempp
lis
0
p
q
lis = tempp->next;
oppure
lis = NULL;
tempp
lis
p
q
0
0
Estrazione da lista
BOOL esttesta(rec*& lis, int& a)
{
rec* p = lis;
if (lis != NULL) {
a = lis->info;
lis = lis->next;
delete p;
return T;
}
return F;
}
Estrazione dalla testa
Estrazione da lista
BOOL estfondo(rec*& lis, int& a)
{
rec* p = lis;
if (lis != NULL) {
for (rec* q = p; q->next != NULL; q = q->next)
p = q;
a = q->info;
if (q == lis)
lis = NULL;
else
p->next = NULL;
delete q;
return T;
}
return F;
}
Estrazione dal fondo
Estrazione da lista
BOOLEAN togli(rec*&lis, int a)
{
rec* p = lis;
for (rec* q = p; q != NULL && q->info != a; q = q->next)
p = q;
if (q != NULL) {
if (q == lis)
lis = p->next;
p->next = q->next;
delete q;
return T;
}
return F;
}
Estrazione di un elemento specifico
Inserimento in lista
void insfondoR(rec*& lis, int a)
{
if (lis != NULL)
insfondoR(lis->next,a);
else {
rec* p = new rec;
p->info = a;
p->next = NULL;
lis=p
}
}
Inserimento in fondo
versione ricorsiva
void inscrescR(rec*& lis, int a)
{
if (lis != NULL && lis->info < a)
inscrescR(lis->next,a);
else {
rec* p = new rec;
p->info = a;
p->next = lis;
lis=p
}
}
Inserimento in ordine
versione ricorsiva
Estrazione da lista
BOOL estfondoR(rec*& lis, int& a)
{
if (lis != NULL) {
if (lis->next != NULL)
return estfondoR(lis->next,a);
else {
a= lis->info;
delete lis;
lis = NULL;
return T;
}
}
else return F;
}
Estrazione dal fondo
versione ricorsiva
BOOL togliR(rec*& lis, int a)
{
if (lis != NULL) {
if (lis->info != a)
return togliR(lis->next,a);
else {
rec* p = lis;
lis = lis->next;
delete p;
return T;
}
}
else return F;
}
Estrazione di un elemento specifico
versione ricorsiva
Scarica

Liste