Linguaggio C - Dipartimento di Ingegneria dell`Informazione

annuncio pubblicitario
Linguaggio C
Strutture dati lineari: Liste lineari,
Stack e Code. Strutture dati non
lineari: Alberi e Grafi.
1
Liste Lineari
• Insieme ordinato di elementi di uno stesso tipo, tali che
ogni elemento ha un predecessore e un successore, ad
esclusione del primo e dell’ultimo;
1) x0 è il primo elemento della Lista ed è seguito da x1;
2) xi è preceduto da xi-1 e seguito da xi+1, ∀ 1≤ i ≤ n-2;
3) xn-1 è l’ultimo elemento della Lista ed è preceduto da xn-2;
• n è la lunghezza della Lista; se n = 0 la lista è vuota;
• La relazione di precedenza significa che per accedere ad
un elemento bisogna accedere a tutti quelli che lo
precedono (a differenza del vettore);
2
Liste vs Vettori
•
Rispetto ai Vettori, le Liste sono
caratterizzate da:
1) Numero variabile di elementi durante
l’esecuzione del Programma (strutture dati
dinamiche);
2) Consentono una gestione più efficiente della
memoria in termini di spazio allocato.
3) Modalità di accesso ai dati sequenziale (in un
Vettore l’accesso al dato è diretto, in una Lista
per accedere ad un dato occorre prima accedere
a tutti gli elementi che lo precedono).
3
Realizzazione concreta di Liste
mediante strutture concatenate
• Il campo next è rappresentato da un “puntatore” ad una
struttura “nodo” della Lista:
struct nodo
{
tipoinfo info;
struct nodo *next;
};
• Si deve allocare complessivamente una struttura dati con la
seguente configurazione:
first
info0
first è detto “testa della lista”: consente l’accesso alla Lista
next
info1
next
…
info(n-1)
last
NULL
4
Lista con strutture concatenate
con dati letti da file
struct studente
{
char nome[30];
char cognome[50];
int voto_informatica;
struct studente *next;
};
typedef struct studente *p_st;
p_st stud, stud_last, stud_first;
FILE *fp;
file.dat
Mario Rossi 25
Giorgio Bianchi 30
…
…
…
if((fp = fopen("file.dat", "r")) == NULL) exit(0);
stud_first è la testa
stud_first = stud_last = NULL;
della Lista. Inoltre
while(!feof(fp))
{
abbiamo stud_last che
stud = (struct studente *)malloc(sizeof(struct studente)); punta all’ultimo elemento
stud->next = NULL;
della Lista.
fscanf(fp, "%s %s %d\n", stud->nome, stud->cognome,
&(stud->voto_informatica));
if (stud_first == NULL)
stud_first = stud;
Nota: stud_last->next = stud
else
“appende” in coda l’elemento appena allocato,
stud_last->next = stud;
stud_last = stud;
}
mentre stud_last = stud aggiorna il
puntatore all’ultimo elemento.
5
Visualizzazione elementi di una
Lista
…
stud = stud_first; //stud_first contiene l’indirizzo al primo
//elemento della lista
while(stud != NULL)
{
printf("%20s ", stud->nome);
printf("%20s ", stud->cognome);
printf("%2d\n", stud->voto_informatica);
stud = stud->next;
}
6
Stack (o Pila)
• Lista lineare in cui gli inserimenti e le estrazioni
avvengono da un estremo detto testa o top dello
stack (si pensi ad una “pila di libri”);
• Politica di gestione LIFO (Last In First Out);
• Si può realizzare con Vettori o con Liste concatenate.
Top dello stack (TOS)
F
E
Recupero (pop)
(operazione distruttiva)
Inserimento (push)
D
C
B
A
7
Stack (o Pila)
• Un’applicazione molto importante in Informatica è la
gestione della chiamata delle funzioni (o procedure)
nei Linguaggi di Programmazione.
• Si immagini una struttura dove vengono memorizzati:
– L’indirizzo dell’ultima istruzione eseguita nella funzione
chiamante,
– I parametri della funzione chiamata, inizializzati con una
copia dei valori passati dalla funzione chiamante,
– Le variabili dichiarate nella funzione chiamata.
•
Al momento della chiamata di una funzione, il Record
relativo viene inserito (push) nello Stack. Quando la
funzione termina, il Record viene rimosso (pop) e il
controllo torna alla funzione che si trova nel TOS. 8
Realizzazione di stack mediante strutture
concatenate: Algoritmi di inserimento (push) e
estrazione (pop)
void push(tipoinfo x)
{
static struct nodo *first_elem;
struct nodo *elem;
struct nodo *pop()
{
static struct nodo *first_elem;
struct nodo *elem;
elem = (struct nodo *)
malloc(sizeof(struct nodo));
elem->info = x;
elem->next = NULL;
if (first_elem == NULL)
return NULL;
if (first_elem
{
first_elem
}
else
{
elem->next
first_elem
}
== NULL)
first_elem = first_elem->next;
= elem;
return(elem);
elem = first_elem;
}
= first_elem;
= elem;
}
9
Coda
• Lista lineare in cui le estrazioni avvengono da
un lato e gli inserimenti dall’altro;
• Politica di gestione FIFO (First In First Out);
• Si può realizzare con un Vettore o con Liste
concatenate.
A
Recupero
(cancellazione)
B
C
D
E
F
Inserimento
(operazione
distruttiva)
10
Realizzazione di code mediante strutture
concatenate: Algoritmi di inserimento e
estrazione
void push(tipoinfo x)
{
static struct nodo *first_elem,*last_elem;
struct nodo *elem;
struct nodo *pop()
{
static struct nodo *first_elem;
struct nodo *elem;
elem = (struct nodo *)
malloc(sizeof(struct nodo));
elem->info = x;
elem->next = NULL;
if (first_elem == NULL)
return NULL;
if (last_elem == NULL)
{
last_elem = elem;
first_elem = elem;
}
else
{
last_elem->next = elem;
last_elem = last_elem->next;
first_elem = first_elem->next;
elem = first_elem;
return(elem);
}
}
}
11
Proprietà di Liste, Stack e
Code
• Stack e Code si possono realizzare anche mediante
Vettori in cui ogni elemento contiene solo
l’informazione, essendo note le posizioni di
inserimento e di cancellazione;
• Le Liste rappresentano una struttura dati molto più
flessibile rispetto al vettore, in quanto inserimenti e
cancellazioni possono essere effettuati in qualunque
posizione: questa flessibilità è possibile grazie
all’utilizzo dei puntatori.
• Quando di deve privilegiare la velocità di accesso alle
informazioni, meglio utilizzare i Vettori (o Tabelle nel
caso di strutture).
12
Definizione di Albero
•
Un insieme A di elementi si dice Albero se:
A = ∅;
oppure:
A = A (r, A0, A1, …, An-1); r = radice;
Ai = Albero,
detto “sottoalbero i-esimo di A”;
• E’ una definizione ricorsiva:
Struttura dati vuota, oppure costituita da un elemento
detto radice e da altri elementi che formano insiemi
disgiunti chiamati sottoalberi della radice, ciascuno
dei quali è ancora un Albero;
13
Nomenclatura sugli Alberi
• Grado di un nodo:
• Foglia
o nodo terminale:
• Padre:
•
•
•
•
•
•
n° di sottoalberi di quel nodo;
nodo di grado 0;
nodo non terminale da cui discendono
i suoi sottoalberi;
nodi che discendono da uno stesso padre;
Figli:
Radice:
nodo che non discende da altri nodi.
X è discendente di Y se ∃ x0, x1,…,xn-1: Y = x0 x1…xn-1= X e xi è
discendente diretto di xi-1, tale successione si chiama cammino;
Lunghezza del cammino = (n° nodi del cammino – 1);
Livello (o profondità) di un nodo X: è i se il livello del padre di
X è i-1, con la convenzione
che la radice ha livello 0
(definizione ricorsiva);
Altezza = livello massimo delle foglie + 1 (in
pratica equivale al numero dei livelli
dell’Albero);
14
A-B-D = cammino
di lunghezza 2
B
Foglie
D
A = Radice,
Nodo di grado 2
e livello 0
A
E
C
F
C = Nodo di grado 0
e livello 1
B = Nodo di grado 3
e livello 1
D,E,F = Nodi di grado 0
e livello 2
Altezza dell’albero = (Livello max delle foglie + 1) = 3
15
Definizione di Albero binario
• Un insieme A di elementi si dice Albero binario se:
A = ∅;
oppure:
A = A (r, Al, Ar); r = radice;
Al = Albero binario detto “sottoalbero sinistro”;
Ar = Albero binario detto “sottoalbero destro”;
• Struttura dati vuota, oppure costituita da un elemento detto
radice e da due elementi disgiunti che sono a loro volta
alberi binari detti sottoalbero sinistro e destro;
• Albero binario bilanciato: albero nel quale le foglie sono
distribuite al più su due livelli consecutivi k-1 e k, e il
numero di nodi a livello k-1 deve essere 2k-1 (ossia, fino al
livello k-1 l’Albero deve essere completo);
16
Proprietà di Alberi binari
•
•
E’ possibile individuare un limite sulla profondità massima in
funzione del numero dei nodi, nel caso di Alberi binari
bilanciati.
Infatti, se un Albero binario di profondità (massima) D è
bilanciato (ossia completo fino al livello D-1), il numero totale N
dei nodi contenuti rispetta la seguente disequazione:
D −1
N ≥ 1 + ∑ 2d = 2 D
d =0
•
Segue quindi un Limite Superiore relativo alla profondità
massima D dell’Albero binario bilanciato:
D ≤ log2 ( N )
17
Realizzazione concreta di
un Albero binario
• Mediante strutture concatenate contenenti
– Un campo informazione;
– Due campi puntatore ai nodi radice del sottoalbero
sinistro e destro.
r
struct nodo
{
tipoinfo info;
struct nodo* left;
struct nodo* right;
}
struct nodo *r;
left
left
info
right
info
right
left
info
right
18
Visita di Alberi binari
•
Attraversamento di tutti i nodi di un Albero binario,
secondo particolari criteri:
1.
2.
3.
Visita Anticipata (radice, sottoalbero sinistro,
sottoalbero destro);
Visita Simmetrica (sottoalbero sinistro, radice,
sottoalbero destro);
Visita Differita (sottoalbero sinistro, sottoalbero
destro, radice).
19
Visita Anticipata
•
visita_anticipata(A)
1. Passo base:
-
se A = ∅ ⇒ esci;
a
A
b
2. Passo di induzione:
-
•
c
d
e
f
si visita la radice;
visita_anticipata(Al) //Al sottoalbero sinistro;
visita_anticipata(Ar) //Ar sottoalbero destro;
Risultato visita anticipata: abdefc
20
Visita Anticipata
void visita_anticipata(struct nodo *r)
{
if (r == NULL)
return;
printf(“%s\n”, r->info);
visita_anticipata(r->left);
visita_anticipata(r->right);
}
21
Visita Simmetrica
a
A
•
b
visita_simmetrica(A)
1. Passo base:
-
se A = ∅ ⇒ esci;
2. Passo di induzione:
-
•
c
d
e
f
visita_simmetrica(Al) //Al sottoalbero sinistro;
si visita la radice;
visita_simmetrica(Ar) //Ar sottoalbero destro;
Risultato visita simmetrica: defbca
22
Visita Simmetrica
void visita_simmetrica(struct nodo *r)
{
if (r == NULL)
return;
visita_simmetrica(r->left);
printf(“%s\n”, r->info);
visita_simmetrica(r->right);
}
23
Visita Differita
a
A
•
visita_differita(A)
1. Passo base:
-
b
c
d
se A = ∅ ⇒ esci;
e
2. Passo di induzione:
-
•
f
visita_differita(Al) //Al sottoalbero sinistro;
visita_differita(Ar) //Ar sottoalbero destro;
si visita la radice;
Risultato visita differita: fedcba
24
Visita Differita
void visita_differita(struct nodo *r)
{
if (r == NULL)
return;
visita_differita(r->left);
visita_differita(r->right);
printf(“%s\n”, r->info);
}
25
Altezza Albero binario
•
altezza_albero (A)
A
b
1. Passo base:
-
se A = ∅ ⇒ restituisci 0;
2. Passo di induzione:
- restituisci
a
c
d
e
f
altezza = 1 + max(altezza_albero(Ar),
altezza_albero(Al));
•
altezza_albero (A) ⇔ 5
26
Altezza Albero binario
int altezza_albero(struct nodo *r)
{
if (r == NULL)
//Passo base
return 0;
//Passo induzione
return (1 + max(altezza_albero(r->left),
altezza_albero(r->right)));
}
NOTA: max non è definita nella Libreria
Standard.
27
Albero binario di ricerca
Definizione:
Un Albero binario si dice di ricerca se è vuoto
oppure:
è costituito da una radice e due sottoalberi di ricerca sinistro e destro
tali che:
– gli elementi del sottoalbero sinistro sono “minori” della radice;
– gli elementi del sottoalbero destro sono “maggiori” della radice;
F
Es:
B
A
1
L
D
C
5
-2
7
3
4
28
Costruzione di un Albero
binario di ricerca
struct nodo
{
tipoinfo info;
struct nodo *left;
struct nodo *right;
};
typedef struct nodo *p_n;
p_n inserisci_nodo(p_n root, tipoinfo x)
{
if (root == NULL)
{
root = (struct nodo *)malloc(sizeof(struct nodo));
root->info = x;
root->left = NULL; root->right = NULL;
}
else
{
if (x < root->info)
root->left = inserisci_nodo(root->left, x);
else
root->right = inserisci_nodo(root->right, x);
}
return root;
}
29
Grafi
• Grafo diretto o orientato:
– Insieme N di elementi detti nodi e insieme
di archi. Un arco è una coppia ordinata
(v,w) di nodi (v,w ∈ N);
• Grafo non orientato:
– Insieme N di nodi e insieme di archi. Un
arco è una coppia non ordinata (v,w) di
nodi (v,w ∈ N);
30
Nomenclatura sui Grafi
• Se (v,w) ∈ N sono collegati da un arco, si dice che v è
adiacente a w;
• Cammino di un grafo: sequenza di nodi v0, v1, …, vn-1, tali che
(v0, v1), (v1, v2), …, (vn-2, vn-1) sono archi;
• Lunghezza del cammino: (n° di archi del cammino),
oppure (n° nodi del cammino – 1);
• Cammino semplice: cammino in cui tutti i nodi, eccetto
eventualmente il primo e l’ultimo, sono distinti;
31
ABCD = cammino
semplice
A
B
C
E
Lunghezza(ABCD) = 3
D
F
32
Realizzazioni concrete
di un Grafo
• Matrice di adiacenza A = A(aij)
– aij = 1 ⇔ nodo i è connesso a nodo j
– aij = 0 ⇔ nodo i non è connesso a nodo j
Proprietà:
la matrice di adiacenza di un grafo non diretto è
simmetrica (aij = aji)
• Liste di adiacenza:
– Ogni nodo è descritto da una lista di elementi
contenente il nome dei nodi ad esso adiacenti;
33
Realizzazione mediante
Matrice di Adiacenza
A
B
C
D
E
F
A B C
1
D
E
1
F
B
1
1
A
C
E
1
D
1
F
34
Realizzazione mediante
Liste di Adiacenza
A
B
E
B
C
NULL
C
D
D
NULL
E
F
F
NULL
E
NULL
A
NULL
B
C
NULL
D
E
F
35
Algoritmi di visita per i Grafi
• Si dice visita di un Grafo un insieme di cammini con
origine in uno stesso vertice;
ovvero:
Attraversamento di tutti i nodi di un Grafo secondo un
particolare criterio;
• Si distinguono due tipi di visita di un Grafo:
– In profondità;
– In ampiezza.
36
Grafi: visita in profondità
•
visita_profondità(G)
1. Passo base
se G = Ø ⇒ esci;
2. Passo di induzione
visita il nodo G se non è stato visitato
per ogni nodo adiacente G->adj
visita_profondità(G->adj);
•
Osservazione: la visita in profondità è strettamente
legata alla politica di gestione di uno Stack (LIFO).
37
Risultato visita in profondità
A
ABCDEF
B
C
D
E
F
38
Grafi: visita in ampiezza
•
visita_ampiezza(G)
si inserisce il nodo di partenza in una coda;
per ogni nodo non visitato:
- si estrae dalla coda un nodo e si visita;
- si inseriscono in coda tutti gli elementi
adiacenti al nodo corrente.
•
Osservazione: la visita in ampiezza è strettamente
legata alla politica di gestione di una Coda (FIFO).
39
Risultato visita in ampiezza
A
ABECFD
B
C
D
E
F
40
Scarica