Sei sulla pagina 1di 10

2

Lista concatenata
 Struttura dati in cui ogni oggetto ha un
collegamento ad un altro oggetto (linked list )
Liste  Il collegamento è un puntatore con l’indirizzo
di memoria dell’altro oggetto
head
15 33 62
Ver. 2.4

© 2010 - Claudio Fornaro - Corso di programmazione in C

3 4

Strutture autoreferenzianti Strutture autoreferenzianti


 struct nodo  E’ possibile definire un puntatore ad un tipo
{ valore next incompleto, quindi ad esempio a una struttura
int valore; che verrà definita successivamente, come nel
struct nodo *next; caso precedente
};  Per lo stesso motivo è lecito utilizzare la
next è un puntatore al tipo di oggetto che si dichiarazione con typedef seguente:
sta dichiarando in questo stesso momento, typedef struct nodo * NodoP;
non essendo la dichiarazione della struct typedef struct nodo
ancora completa si dice avere un {
tipo incompleto int valore;
NodoP next;
} Nodo;
5 6

Strutture autoreferenzianti Testa e coda della lista


 Per lo stesso motivo, è possibile definire  struct nodo
strutture mutuamente referenzianti: {
struct due int valore;
{ struct nodo *next;
int datoDue; } *head = NULL;
struct uno *puntUno;
};
struct uno  head è un puntatore a struct nodo, tutta
{ la lista inizierà da questo puntatore
int datoUno;
struct due *puntDue;  La lista termina con un elemento in cui in
}; membro next vale NULL
 Un tipo di dato (come struct uno) utilizzato  La lista è inizialmente vuota, quindi head
prima di conoscerne la dim. è detto incompleto viene inizializzato a NULL

7 8

Inserimento in testa Inserimento in testa


 Situazione iniziale  Si crea la nuova struct nodo da inserire:
Caso generico: la lista non è vuota p=malloc(sizeof(struct nodo))
head head
15 33 62 15 33 62

p p
... ? ?

x x
88 88
9 10

Inserimento in testa Inserimento in testa


 Si immette il valore da memorizzare nella lista:  Si fa puntare il nuovo elemento al 1o:
p->valore = x; p->next = head;
head head
15 33 62 15 33 62

p p
88 ? 88

x x
88 88

11 12

Inserimento in testa Inserimento in testa


 Si fa puntare head al nuovo elemento: Riassumendo:
head = p; if ((p=malloc(sizeof(struct nodo)))!= NULL)
head {
15 33 62 p->value = x;
p->next = head;
head = p;
p }
88

x
88
13 14

Rimozione dalla testa Rimozione dalla testa


 Situazione iniziale  Si preleva il valore dal 1o elemento:
x = head->valore;
head head
15 33 62 15 33 62

p p
... ...

x x
... 15

15 16

Rimozione dalla testa Rimozione dalla testa


 Si salva il puntatore all’elemento da rimuovere:  Si fa puntare head al 2o elemento:
p = head; head = head->next;
head head
15 33 62 15 33 62

p p

x x
15 15
17 18

Rimozione dalla testa Rimozione dalla testa


 Si dealloca lo spazio del 1o elemento:  Situazione finale
free(p);
head head
free! 33 62 33 62

p p
?

x x
15 15

19 20

Rimozione dalla testa Considerazioni


Riassumendo:  Una lista di strutture, rispetto ad un vettore di
if (head != NULL) strutture:
{
 Non ha problemi di dimensioni: varia all’occorrenza
x = head->valore;
p = head;  Lo scambio di elementi è molto veloce (copia di
head = head->next; puntatori)
free(p);  L’accesso è più lento (deve percorrere la lista)
}
21 22

Esercizi Esercizi
1. Scrivere in un file separato (con controlli di 2. Utilizzando le funzioni dell’esercizio 1 (senza
errore) le seguenti funzioni di gestione di una modificare il file), scrivere le funzioni
lista dinamica di int stabilendo per esse push/pop ed enqueue/dequeue con gli stessi
appropriati argomenti e valori restituiti. La identici prototipi definiti per stack e coda
struttura dati sia static. Produrre un main realizzata con vettori. Il main deve essere lo
a menu per il test. stesso identico usato con le realizzazioni con i
 AddToHead() vettori (e quindi compatibile con la nuova
 AddToTail()
realizzazione).
Per evitare l’overhead della doppia chiamata
 RemoveFromHead()
a funzione (es. la chiamata a push che a sua
 RemoveFromTail()
volta chiama AddToHead), le funzioni richieste
 ClearAll()  svuota la lista vengano realizzate come macro (nel file .h da
includere nel main).

23 24

Variazioni Homework 8
 Lista con valori dummy Scrivere in un file separato le funzioni di
Per semplificare il codice di alcune operazioni gestione di una lista di int, passando anche la
(meno casi particolari) può essere utile che la testa della lista (per avere più liste).
lista abbia uno o più elementi fittizi aggiuntivi La lista potrà avere due stati: ordinata o non
(non contengono valori significativi): ordinata; alcune funzioni riordineranno
 come primo elemento fisso puntato da head preventivamente la lista (se necessario), altre la
 come ultimo elemento fisso della lista (sentinella) lasceranno non più ordinata; alcune funzioni
 Lista circolare lavoreranno in modo diverso a seconda che la
L’ultimo elemento punta al primo lista sia ordinata o no.
 Multi-lista  Può essere utile avere un puntatore all’ultimo
Ogni elemento della lista ha due o più elemento e un contatore degli elementi
puntatori ai nodi successivi, ci possono essere  Ricordarsi di eliminare (free) tutti gli elementi che
quindi più modi di percorrerla (es. diversi non servono più
ordinamenti) Si scriva un main di test
25 26

Homework 8 Homework 8
(Continuazione) (Continuazione)
 Funzioni di base  Funzioni di ordinamento e ricerca
 listAddToHead (rende la lista non ordinata)  listSort (ascendente e discendente)
 listAddToTail (rende la lista non ordinata)  listAddOrdered (aggiunge in lista ordinata al
 listRemoveFromHead (e ne restituisce il valore) posto giusto, se necessario la lista venga ordinata)
 listRemoveFromTail (e ne restituisce il valore)  listFind (cerca il valore dato nella lista a partire
 listClearAll (svuota la lista) dall’elemento di indice indicato, restituisce l’indice
della prima occorrenza, se la lista è ordinata deve
 listCount (restituisce il numero degli elementi
fermarsi appena scopre che l’elemento non c’è)
della lista)
 listRemoveValue (rimuove dalla lista l’elemento
 listTestIsEmpty (test se la lista è vuota)
contenente la prima occorrenza del valore dato e lo
restituisce)

27 28

Homework 8 Homework 9
(Continuazione) Per accedere ai singoli elementi della lista si può
 Funzioni assolute di scansione della lista definire un “cursore” (un puntatore) che si
 listGetValueAt (restituisce il valore riferisca (punti) ai singoli elementi della lista
dell’elemento di indice i, il primo abbia indice 1) Il cursore può essere spostato avanti e indietro
 listSetValueAt (cambia il valore dell’elemento nella lista per identificare un particolare
di indice i) elemento.
 listAddAt (aggiunge un elemento in modo che
Si continui l’Homework precedente aggiungendo
occupi la posizione di indice i, facendo avanzare gli
altri elementi) le seguenti funzioni per l’uso dei cursori. Si
 listRemoveAt (elimina l’elemento di indice i) faccia attenzione a gestire correttamente i
cursori quando la lista viene modificata con le
altre funzioni e viceversa.
Il main di test sia identico a quello dell’Hw 8
29 30

Homework 9 Homework 9
(Continuazione) (Continuazione)
 Funzioni di scansione della lista con cursore  Funzioni di scansione della lista con cursore
 listGetCursor (restituisce l’indice dell’elemento  listAddAtCursor (aggiunge un elemento alla
puntato dal cursore, il primo abbia indice 1) posizione del cursore, facendo avanzare gli altri
 listSetCursor (posiziona il cursore all’elemento elementi)
specificato dall’indice fornito)
 listRemoveAtCursor (elimina l’elemento
 listMoveCursor (muove il cursore al primo puntato dal cursore)
elemento, al successivo, al precedente, all’ultimo –
utilizzare una enum per dare i nomi delle posizioni  listTestCursorAtFirst (test se punta al
“primo”, “successivo”, etc.) primo elemento)
 listGetValueAtCursor (restituisce il valore  listTestCursorAtLast (test se punta all’ultimo
dell’elemento puntato dal cursore) elemento)
 listSetValueAtCursor (cambia il valore
dell’elemento puntato dal cursore)

31 32

Liste bidirezionali Homework 10


 La bidirezionalità si ottiene mediante un link al Come gli Homework 8 e 9, ma si usino liste
nodo precedente (doubly-linked list ) bidirezionali.
struct nodo Il main deve essere identico a quello degli
{ prev valore next Homework 8 e 9.
int valore;
struct nodo *prev;
struct nodo *next;
} *head=NULL;

head
15 22 99 67
33 34

Liste di liste Considerazioni


 Alcuni dei membri dei nodi di una lista  Problema: in uno stesso programma servono
principale sono le teste di liste secondarie liste con nodi di tipo diverso
head  Ad es., in una lista di liste la lista principale e
11 22 33 quelle secondarie hanno nodi di tipo diverso
 Soluzione dei linguaggi procedurali (C): una
a e serie di funzioni diverse per ciascun tipo di lista
 Soluzione ideale: una serie di funzioni
generiche che gestiscano qualsiasi tipo di
y elemento (oggetto)

Si
Si usano allora linguaggi
usano allora linguaggi ad
ad oggetti
oggetti

35
Tabelle di hash 36

Tabelle di hash Esempio


 Per velocizzare la ricerca di un elemento in  Esempio
una lista, si distribuiscono gli elementi su più Suddivisione dei valori in base alla cifra delle
liste (più corte quindi più veloci da utilizzare) unità
hashtab:
 Le teste di queste liste sono in un vettore di 9
puntatori 8
 La scelta di quale lista debba ospitare i singoli 7 77
elementi viene stabilito da un opportuno 6
5
calcolo (funzione di hash) sull’elemento
4 14 164
stesso, il risultato è l’indice che in quel vettore 3
indica quale lista usare 2
1 11
0 120 60 90
Tabelle di hash 37 38

Esempio Tabelle di hash


 La funzione di hash è:  Una buona funzione di hash genera valori con
int hash(int n){ distribuzione uniforme, ossia le liste prodotte
return abs(n) % 10;} hanno lunghezze più o meno uguali
 Il vettore di puntatori è:  Esempio di funzione di hash per stringhe:
static struct nodo *hashtab[10]; unsigned hash(char *s)
 La testa della lista relativa ad x è: {
hashtab[hash(x)] unsigned hashval = 0;
for ( ; *s != '\0'; s++)
 Per trovare x nella lista che gli compete:
listFind(hashtab[hash(x)], x); hashval = *s + 31 * hashval;
return hashval % HASHSIZE;
 Per inserire x nella lista che gli compete: }
listAdd(hashtab[hash(x)], x);

39

Homework 11
Si scriva un programma per generare l’indice
analitico di un (lungo) file di testo. Il programma
deve memorizzare le singole parole (si usi una
lista ordinata per ciascuna delle lettere iniziali) e
per ciascuna deve memorizzare i numeri di tutte
le righe dove è presente (sotto-lista ordinata).
Un secondo file di testo elenca le parole da non
tenere in considerazione (es. articoli,
preposizioni, etc.), per questa si usi un
opportuna funzione hash. Il risultato della
generazione dell’indice analitico sia scritto in un
file.