Title: Strutture dati elementari
1Strutture dati elementari
- Parte 6
- Vettori e ricerca binaria
- Matrici e triangolo di Tartaglia
- Records (cenni)
Corso A Prof. Stefano Berardi
http//www.di.unito.it/stefano Corso B Prof.
Ugo de Liguoro http//www.di.unito.it/deligu
2Un quadrato magico di numeri
Albert Durer. Melencolia,1514 (dettaglio)
16 3 2 13
5 10 11 8
9 6 7 12
4 15 14 1
3Indice Parte 6 i Vettori
- Strutture dati i vettori.
- Esempi elementari stampa, somma, test di
uguaglianza, inversione per vettori. - Esempi più complessi ricerca lineare e binaria
per vettori. - Matrici il triangolo di Tartaglia.
- Records e vettori parzialmente riempiti (cenni).
41. Strutture dati I Vettori
- In generale, le strutture dati sono un modo per
rappresentare insiemi di informazioni nella
memoria di un computer - Una semplice variabile è un caso banale di una
struttura dati, un modo per rappresentate una
singola informazione. - In questa parte del corso studieremo strutture
dati per rappresentare insiemi di dati di tipo
omogeneo, i vettori e le matrici, e per
rappresentare dati di tipo eterogeneo, i record.
5Vettori (o array) in C
- Un vettore v (array) è una sequenza di n oggetti
tutti del medesimo tipo, detti elementi del
vettore, per qualche ngt0 detto la dimensione del
vettore. - Gli elementi del vettore sono indicizzati
utilizzando interi positivi, da 0 fino a n-1,
immaginati disposti come segue - v0, v1, , vn-1
6Notazioni per i vettori
- Le notazioni che seguono NON sono codice C/C,
ma sono la notazione che usiamo quando parliamo
dei vettori - i..j k?Z i ? k ? j intervallo di interi
- vi..j vi, vi1, , vj
- Quindi, se i gt j allora i..j lista vuota se un
vettore v ha n elementi questi formano linsieme
v0..n-1.
7I vettori nella memoria RAM
- Gli elementi di un vettore occupano spazi di
memoria consecutivi nella memoria RAM della
macchina di Von Neumann. Lindirizzo vi di
ogni elemento si calcola dunque con la formula
vi v0 id, dove d sizeof (tipo di
v) -
d dimensione di ogni elemento (es. 4 bytes)
vi indirizzo vi bid
v0 ind. b
v1 ind. bd
v2 ind. b2d
v
b indirizzo base v0 indirizzo v0
(es.byte n. 1 milione)
i indice di vi (detto anche spiazzamento ).
Es. i100, indirizzo di vi 1 000 400.
8Vettori dichiarazione
- Un vettore v in C è una costante di tipo
indirizzo. Viene identificato con v0,
lindirizzo del suo primo elemento. La
dichiarazione di v consiste nellindicazione del
nome e del tipo degli elementi del vettore, e
nellindicazione del loro numero, detto la
dimensione del vettore. Con sizeof(v) si indica
invece il numero di bytes occupati dal vettore
int v100
9Vettori lerrore più comune
- La dimensione di un vettore deve essere una
costante o una variabile già assegnata. - Quindi le seguenti righe sono errate (ma
purtroppo il compilatore non lo segnala!)
int dim double wdim / ERRORE la variabile
dim, per il solo fatto di essere dichiarata, ha
un valore, ma non si può prevedere quale. Quindi
si crea un vettore di dimensione casuale, a
volte di miliardi di elementi, producendo in
questultimo caso un crash di programma /
10Accesso agli elementi di un vettore
- Per accedere ai singoli elementi di un vettore si
usano gli indici attenti a non confondere gli
indici con la dimensione che compare nella
dichiarazione.
Gli interi tra hanno diverso significato in
una dichiarazione e in una assegnazione nella
dichiarazione, 100 è la dimensione,
nellassegnazione, 0 è un indice
int v100 / dichiarazione / v0 6 /
assegnazione, accesso in scrittura / int n
v0 / assegnazione, accesso in lettura /
11Inizializzazione attraverso un ciclo
- Il programma che segue inizializza a 0 tutti gli
elementi del vettore v
int v100 for (int i 0 i lt 100 i) vi
0
Prima dellinizializzazione, tutti gli elementi
di v, avendo un indirizzo di memoria, hanno
comunque un valore, ma un valore casuale
provate a stamparli.
12Inizializzazione attraverso una dichiazione
- Il C consente di definire (quindi anche di
inizializzare) un vettore elencandone gli
elementi. Non è necessario indicare il numero
degli elementi, che viene calcolato
double a 22.2, 44.4, 66.6 // alloca un
vettore a di 3 float con // a0 22.2, a1
44.4, a2 66.6
13Attenti a non uscire dai limiti di un vettore
- Se un vettore ha dimensione d ed ilt0, oppure i ?
d, allora vi non dovrebbe essere definito. - Invece, in C/C, vi esiste sempre, è per
definizione il contenuto dellindirizzo di
memoria calcolato dalla formula - v0 i sizeof (tipo di v)
- ossia un valore a caso!! Questa convenzione è
introdotta per semplicità di calcolo. Il
compilatore non ci avvisa quando utilizziamo un
vi con ilt0, oppure i ? d, sta a noi evitare che
accada.
142. Alcuni semplici esempi stampa di un vettore
- La stampa di un vettore deve avvenire elemento
per elemento (dunque usando ad es. un ciclo for)
int v100 for (int i 0 i lt 100 i)
cout ltlt vi
15Somma e media di un array di numeri
Anche la somma di un vettore deve avvenire
elemento per elemento (dunque usando ad es. un
ciclo for)
double v100 double somma 0 for (int i 0
i lt 100 i) somma somma
vi double media somma/100.0 /dividiamo per
un numero reale per evitare larrotondamento/
16Un test di uguaglianza errato
- Se applichiamo il test a due vettori distinti
otteniamo risposta costantemente uguale a false
int v100, w100 if (v w) / v, w sono
identificati con gli indirizzi dei loro primi
elementi, dunque il test confronta questi
ultimi. Dato che v, w sono allocati in
posizioni diverse della memoria, i loro primi
elementi hanno diversi indirizzi, anche quando v,
w hanno elementi di valore uguale. Dunque vw
vale sempre false. /
17Un test di uguaglianza corretto
- Per decidere se due array di egual tipo e egual
dimensione N hanno valori uguali per indici
uguali, si devono confrontare tutti gli elementi
usando uniterazione. Ecco una soluzione con un
WHILE
/ Il ciclo while trasporta il contatore i al
primo indice per cui vi!wi, se ne esiste
uno, altrimenti trasporta i fino ad N / int i
0 while (i lt N vi wi) i if (i N)
cout ltlt "v,w hanno elementi uguali per indici
uguali" else / i lt N / cout ltlt "v, w hanno
diverso lelemento di posto" ltlt i
18Un test di uguaglianza corretto 2
- Per decidere se due array di egual tipo e egual
dimensione N hanno valori uguali per indici
uguali, si devono confrontare tutti gli elementi
usando uniterazione. Ecco la traduzione della
soluzione precedente in un ciclo FOR
int i for (i0 i lt N vi wi i)
//il corpo del FOR e vuoto if (i N) cout ltlt
"v,w hanno uguali per indici uguali" else cout
ltlt "v, w hanno diverso lelemento di posto" ltlt i
19Passaggio di un vettore ad una funzione
- Un array viene passato alle funzioni sempre per
indirizzo. Attenzione nella dichiarazione del
parametro scriviamo int a, nella chiamata non
ripetiamo il tipo e scriviamo solo a
int sum( int a, int n) / prec.0ltnltdim.a
post.condsum(a,n) somma di a0..n-1/ int
i,s for (i0,s0 iltn i) ssai return
s int main() int a 11, 33, 55, 77 int
size sizeof(a)/sizeof(int) //num.el. di a cout
ltlt"sum(a,size) "ltltsum(a,size)ltltendl
20Modifica di un vettore
- Poiché un vettore viene passato per riferimento
(indirizzo) se una chiamata di funzione modifica
gli elementi del vettore, queste modifiche sono
permanenti
void scambia (int v, int i, int j) // pre 0 ?
i, j lt dimensione di v // post scambia vi con
vj int temp vi vi vj vj
temp
21Inversione di un vettore v
- Idea. Poniamo due indici i, j agli estremi di v e
muoviamo i, j uno verso laltro. Scambiamo tra
loro gli elementi di posto i e j, fino a che
tutti gli elementi alla sinistra di v sono
scambiati con tutti gli elementi alla destra di v.
void inverti (int v, int n) / PRE 0 ? n ?
dimensione di v. POST elementi v0..n-1 in
ordine inverso / for (int i 0, int jn-1
iltj i,j--) scambia(v, i, j)
i0
i1
jn-2
jn-1
223. Ricerca Lineare e Binaria
- Si vuole decidere se un intero n appartiene alla
sequenza rappresentata dal vettore v. Il metodo
più semplice è la ricerca lineare confrontiamo n
con v0, v1, v2, in questordine.
bool Member (int n, int v, int dim) / pre la
dimensione di v e gt dim gt0. post restituiamo
true ? esiste i tale che vin / bool trovato
false / trovato indica se n e gia stato
trovato. trovato puo cambiare solo da false a
true, mai viceversa./ for(int i0iltdimi)if(v
in)trovatotrue return trovato
23Ricerca lineare con interruzione uso di una
variabile flag
- Per efficienza, potremmo voler interrompere la
ricerca di n non appena troviamo n. A tal fine,
definiamo un valore booleano trovato che parte
da vero, e non appena trovato vale vero
usciamo. Il test del ciclo quindi deve essere
continuiamo se !trovato è vero. Una variabile
booleana che ci avvisa quando uscire da un ciclo
è detta una flag.
bool Member (int n, int v, int dim) / pre la
dim. di v e gt dim gt0. post true sse esiste i
t.c. vi n/ bool trovato false //detta
variabile flag for (int i0 iltdim !trovato
true i) if (vi n) trovato true
return trovato
24Ricerca lineare con interruzione seconda
soluzione
- Possiamo eliminare la flag trovato, spostando
(la negazione di) vi n nel test del for. In
tal caso, dobbiamo dichiarare i fuori dal for
bool Member (int n, int v, int dim) // pre la
dimensione di v e gt dim gt 0 // post true sse
esiste i t.c. vi n int i for (i0 i
lt dim vi ! n i) return (i lt dim)
25Ricerca binaria
- Se un vettore (per es. di interi) è ordinato in
senso crescente, la ricerca di un elemento nel
vettore può essere enormemente accelerata
sfruttando il metodo della Ricerca Binaria - Manteniamo due indici, i e j, a delimitazione
della porzione vij di v in cui cercare - Ad ogni passo confrontiamo n con il valore di
indice medio in i..j, cioè m(ij)/2 - Se vm ! n allora cerchiamo in vi..m-1 o in
vm1..i a seconda che n lt vm oppure vm lt n. - Daremo ora una descrizione dettagliata del
funzionamento della ricerca binaria, in 5 tappe.
26Ricerca binaria 1 pre- e post-condizioni
- 1. Definiamo il problema. Input il valore
cercato in v è n. Pre-condizione il vettore v
di dimensione dim è ordinato. - Post-condizione restituire lindice del valore
cercato (un i tale che vin, se esiste),
altrimenti la lunghezza dim di v (dim sta per
non trovato)
0
dim-119
0
dim-119
Valore cercato n 25. Indice del valore
cercato 7 ( v725)
27Ricerca binaria 2 una proprietà invariante
- 2. Individuiamo una proprietà invariante della
ricerca binaria, cioè una proprietà significativa
che resta vera durante tutta la durata della
ricerca binaria
- Propr. Invariante durante la ricerca binaria
considero solo dei segmenti vij di v tali che
se n è in v, allora n è in vij.
vij
indice 0
indice 19
i3
j16
Cerco n25 in vij
Per es. se n25 è in v, allora n è in vi..j
v3..16.
28Ricerca binaria 3 il funzionamento
- 3. La ricerca binaria cerca un modo per
avvicinarsi alla soluzione mantenendo vero
linvariante
- Passo generico dividiamo il sottovettore in due
parti (quasi) uguali. Caso 1. Se n si trova nel
punto intermedio restituisco m
Spazio dove avviene la ricerca di n
0
dim-1 19
Se il valore cercato è n 43 allora lho trovato
Punto intermedio m di ij
29Ricerca binaria 3 il funzionamento
- 3. La ricerca binaria cerca un modo per
avvicinarsi alla soluzione mantenendo vero
linvariante
- Passo generico dividiamo il sottovettore in due
parti (quasi) uguali. Caso 2. Se il valore n
cercato è lt di quello nel punto intermedio,
allora, dato che il vettore è ordinato, n si
trova nella parte sinistra di vij.
Spazio di ricerca
0
19
Punto intermedio m di i..j
Valore cercato n 25
30Ricerca binaria 3 il funzionamento
- 3. La ricerca binaria cerca un modo per
avvicinarsi alla soluzione mantenendo vero
linvariante
- Passo generico dividiamo il sottovettore in due
parti (quasi) uguali. Caso 3. Se il valore
cercato n è gt di quello nel punto intermedio,
allora n si trova nella parte destra di vij .
Spazio di ricerca
0
19
Punto intermedio m di i..j
Valore cercato n 60
31Ricerca binaria 4 la fine della computazione
- 4. Definiamo in quale momento la computazione
si deve fermare
- Quando si sia trovato il valore nel punto
intermedio di indice m, oppure .
Spazio di ricerca
0
19
Valore cercato (e trovato) n 43
Punto intermedio di indice m
32Ricerca binaria 4 la fine della computazione
- 4. Definiamo in quale momento la computazione
si deve fermare
- . oppure quando il sottovettore cui limitiamo la
ricerca sia ridotto al vettore vuoto (cioe al
vettore vij con igtj)
0
19
n23 dovrebbe essere qui in mezzo, tra 21 e 25
ma questo intervallo è vuoto. Il valore n non
viene trovato.
Valore cercato (e non trovato) n23
33Ricerca binaria 5linizio della computazione
- 5. Definiamo le condizioni iniziali per la
ricerca binaria
- Allinizio, il segmento vij di vettore in cui
cercare n e lintero vettore n.
Spazio di ricerca iniziale v0..n-1
0
19
34Ricerca binaria i dettagli della codifica dei
dati
- Stabiliamo ora i dettagli della codifica del
segmento di vettore Vi..j che usiamo durante la
ricerca.
- Il sottovettore Vi..j, a cui limitiamo la
ricerca, è compreso tra le posizioni i e j incluse
Spazio di ricerca in un passo generico della
computazione
m
i
j
0
19
- Il punto medio m ha indice (i j) diviso 2
- Se i gt j allora il sottovettore Vi..j è vuoto
35Ricerca binaria limplementazione
- Scriviamo ora una funzione C che implementa la
ricerca binaria, usando pre- e post-condizioni e
linvariante come commenti. La funzione ottenuta
è decisamente breve rispetto a tutta la
discussione che è servita a presentarla.
36Ricerca binaria limplementazione
int binsearch (int n, int v, int dim) // pre
la dim. di v e gt dim e v0..dim-1 è ordinato
// post i tale che vi n se ne esiste uno,
altrimenti dim int i 0, j dim - 1, m
while (i lt j) //inv. se n in v0..n-1 allora n
in Vi..j m (ij)/2 if (vm n)
return m else if (n lt vm) j m - 1
else / (vm lt n) / i m 1 return
dim // dim sta per non trovato
374. Matrici in C
- Una matrice a due dimensioni viene vista come un
vettore bidimensionale, o di vettori, ognuno dei
quali rappresenta una riga della matrice la
dichiarazione di una matrice è simile a quella
vettore, eccetto che richiede due dimensioni - double A1020
- // matrice 10righe x 20colonne di double
- Aij 7.23
- // se 0 ? i lt 10 e 0 ? j lt 20 allora
- // scrive 7.23 come valore della
- // i-esima riga e j-esima colonna di A
38Passaggio di un vettore bidimensionale a funzione
- Nei parametri formali di una funzione un vettore
V di dimensione 1 può figurare come - void f(int V, int n)
- senza lindicazione della lunghezza di V dentro
int V la lunghezza n del vettore è a sua volta
un parametro, che può cambiare da una chiamata
allaltra. - Nel caso di una matrice A di due o più
dimensioni, invece, le dimensioni debbono essere
costanti indicate dentro A stesso, come segue
void g(double A 1020 )
39Passaggio di un vettore bidimensionale a funzione
- Questo tipo di dichiarazione è molto scomoda
una funzione g che stampa una matrice A di 10x20
elementi non può essere utilizzata per stampare
una matrice B di 20x20 elementi, perchè double
A1020 e double B2020 sono due tipi
diversi. - La seconda dimensione di A, ovvero il numero
20 delle colonne di A, è purtroppo necessaria per
ricostruire lindirizzo della variabile Aij
nella macchina di Von Neumann, e non è
ricostruibile a partire dalla sola variabile A.
void g(double A 1020 )
40Un esempio di uso di matriciil triangolo di
Tartaglia
Scriveremo ora una funzione che assegna le prime
n1 righe del triangolo diTartaglia a una matrice
(n1)x(n1). Per definizione, ogni elemento posto
ai lati del triangolo di Tartaglia vale 1, e ogni
altro elemento è la somma dellelemento posto
sopra e di quello posto sopra e a destra.
Niccolò Tartaglia 1499-1557
41Un esempio di uso di matriciil triangolo di
Tartaglia
0 1 2 3 4 5
6
Righe da 0 a 6 del Triangolo in una matrice A
di 7x7. Per definizione 1 A00 A1,0
A20 Ai,0 e 1 A00 A1,1
A22 Ai,i e per 0ltjlti Aij
Ai-1j-1Ai-1j.
1 1 1 1 2 1 1 3
3 1 1 4 6 4 1 1
5 10 10 5 1 1 6 15
20 15 6 1
0 1 2 3 4
5 6
Per es. 6 A42 A31 A32 3 3.
42Il triangolo di Tartaglia usando una matrice
void Tartaglia(int n) // stampa righe da 0 a n
del triangolo di Tartaglia int an1n1
//definisce matrice (n1)x(n1) for (int i 0
iltn i) // costruzione riga per riga
ai0 1 // assegna 1 a tutta la colonna 0
for (int j 1 jlti j) // assegna colonne da
1 a i-1 aij ai-1j-1
ai-1j aii 1 // assegna 1 a tutta
la diagonale // stampa righe da 0 a n del
triangolo (vedi pagina seguente per sapere
come)
43Stampa del triangolo di Tartaglia
void Tartaglia(int n) // definisce righe da 0 a
n (vedi pagina precedente per sapere come)
// stampa righe da 0 a n for (int i 0 i
lt n i) for (int j 0 j lt i
j) cout ltlt setw(4) ltlt aij
cout ltlt endl Listruzione setw(4)
esegue la prossima stampa con almeno 4 spazi.
Richiede di includere la libreria include
ltiomanip.hgt
445. I records (cenni)
Un record è una tupla di valori di tipo
possibilmente diverso (è questa la differenza con
i vettori) a cui accediamo attraverso etichette
anziché indici
struct ltnome strutturagt lttipo1gt ltetichetta
campo1gt ... lttipokgt ltetichetta campokgt
Come concetto matematico, un record corrisponde a
un prodotto cartesiano tipo1 x x tipon mentre
un vettore corrisponde a un insieme potenza
tipon
45I record nella memoria
- I record nella memoria di una macchina di Von
Neumann sono rappresentati con celle adiacenti,
ma di diversa dimensione. E necessario
individuare ogni cella assegnadole un nome
Nome_1
Nome_k
Possiamo rappresentare i razionali come
frazioni, e le frazioni come un record di due
campi numeratore e denominatore. Si tratta solo
di un esempio i razionali non sono una struttura
dati abbastanza complessa da giustificare luso
dei records.
46Frazioni rappresentate da un record
struct Ratio int num int den
Se r e un record che rappresenta una frazione,
indichiamo numeratore e denominatore di r con
r.num e r.den
47Record come valori di funzioni
- Diversamente dagli array, le struct in C sono
passate (e restituite) per valore dunque ogni
chiamata costruisce una copia del record e la
assegna al parametro formale della funzione
Ratio NewRatio(int n, int d) / Pre-cond.
d!0 Post-cond.NewRatio(n,d) restituisce il
record che rappresenta n/d / Ratio r
r.num n r.den d return r int
main() Ratio a NewRatio(2,3) // a2/3
48Record come valori di funzioni
- Un altro esempio, la somma di frazioni. Le
strutture in C sono passate per valore, creando
delle copie dei valori passati.
Ratio SumRatio(Ratio a, Ratio b) // post
restituisce il razionale a b Ratio r r.num
a.num b.den a.den a.num r.den a.den
b.den return r int main() Ratio a, b
Ratio c SumRatio(a,b) // c a b
49Vettori parzialmente riempiti
- Di un vettore occorre ricordare la
dimensione se poi se ne usa solo una parte, come
abbiamo fatto nellesercizio sui numeri primi,
bisogna sapere sin dove è riempito, ovvero,
quale è il prossimo indirizzo libero.
v
prox_libero
Se non ci sono indirizzi liberi, il prossimo
indirizzo libero per definizione è la dimensione
dim del vettore v.
50Vettori parzialmente riempiti come record
- Possiamo definire un record per rappresentare
vettori parzialmente riempiti. Usiamo un record
con due campi un vettore e il primo indirizzo
ancora libero del vettore (se esiste)
struct Array int v10
int prox_libero / prox_libero indice
del prossimo indirizzo libero /
51Funzioni su vettori parzialmente riempiti
Un esempio di una funzione che agisce sui
vettori parzialmente riempiti la stampa di tutti
gli elementi del vettore effettivamente in uso
(dunque fino alla prossima posizione libera
esclusa).
void Mostra(Array a) /post stampa la parte
riempita di v, e cioé (a.v)0..a.prox_libera-1
/ for (int i 0 i lt a.prox_libero i)
cout ltlt a.vi ltlt " " cout ltlt
endl
52Funzioni su vettori parzialmente riempiti
Un altro esempio di una funzione che agisce
sui vettori parzialmente riempiti come
aggiungere un elemento a quelli effettivamente in
uso
bool Aggiungi(int n, Array a) / post se
a.prox_libera lt 10, aggiunge n in ultima pos. e
restituisce true restituisce false altrimenti
/ if (a.prox_libero lt 10) a.va.prox_libero
n a.prox_libero return true else
return false
53Riepilogo
- Vi sono due strutture dati predefinite per
rappresentare collezioni finite di valori
vettori (array) e record (struct) - I vettori hanno dimensione fissa, elementi
omogenei, e sono passati per riferimento - Vettori a due dimensioni rappresentano una
matrice. - I record hanno un numero fisso di campi
individuati da nomi, hanno tipi eventualmente
diversi di valori, e sono passati per valore.