Strutture dati elementari

Slides:



Advertisements
Presentazioni simili
Puntatori Linguaggio C.
Advertisements

Introduzione al linguaggio C++
INFORMATICA Tipi strutturati
I tipi Strutturati.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Array, matrici Marco D. Santambrogio – Ver. aggiornata al 21 Marzo 2013.
Classe III A A.s – 2010 Programma di Informatica
Dati strutturati A. Ferrari.
© 2007 SEI-Società Editrice Internazionale, Apogeo Unità G1 Dati strutturati.
Ripasso R1 Dati strutturati.
Array multidimensionali
Array (vettori) In linguaggio C / C++.
PUNTATORI Introduzione
Dipartimento di Matematica
Algoritmi e Programmazione
1 Informatica Generale Susanna Pelagatti Ricevimento: Mercoledì ore presso Dipartimento di Informatica, Via Buonarroti,
Un nuovo tipo di dati Gli array.
Anno accademico Array e puntatori in C.
Algoritmi in C++ (1) da completare
Fondamenti di Informatica II Ingegneria Informatica / Automatica (A-I) Meccanica Prof. M.T. PAZIENZA a.a – 3° ciclo.
Funzioni definite dall’utente
Fondamenti di Informatica II Ingegneria Informatica (A-I) Prof. M.T. PAZIENZA a.a – 3° ciclo.
Corso di Fondamenti di programmazione a.a.2009/2010
Prof.ssa Chiara Petrioli -- corso di programmazione 1, a.a. 2006/2007 Corso di Programmazione 1 a.a.2006/2007 Prof.ssa Chiara Petrioli Corso di Laurea.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Stringhe e tipi di dati strutturati Marco D. Santambrogio – Ver. aggiornata al.
Process synchronization
Informatica di base A.A. 2003/2004 Algoritmi e programmi
Allocazione dinamica della memoria
Laboratorio di Linguaggi lezione IV Marco Tarini Università dellInsubria Facoltà di Scienze Matematiche, Fisiche e Naturali di Varese Corso di Laurea in.
Laboratorio di Linguaggi P R I M O C O M P I T I N O Marco Tarini Università dellInsubria Facoltà di Scienze Matematiche, Fisiche e Naturali di Varese.
Introduzione alla programmazione lll
APPUNTI SUL LINGUAGGIO C
nome: sequenza di caratteri usata per denotare un oggetto
1 laboratorio di calcolo II AA 2003/04 seconda settimana a cura di Domizia Orestano Dipartimento di Fisica Stanza tel. ( )
Dichiarazioni e tipi predefiniti nel linguaggio C
Fondamenti di Informatica A - Massimo Bertozzi TIPI DI DATO IN C++ What's in a name? That which we call a rose By any other name would smell as sweet.
Le funzioni.
Fondamenti di informatica Linguaggio C Main Program: Architettura di un PC Diagrammi di flusso Linguaggio C.
UNIVERSITA’ STUDI DI ROMA “FORO ITALICO”
2000 Prentice Hall, Inc. All rights reserved. Capitolo 6 (Deitel) I vettori Sommario Introduzione Vettori Dichiarazione di vettori 6.4.
Puntatori e gestione dinamica della memoria
Unità Didattica 3 Linguaggio C
Programmazione di Calcolatori
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Array e stringhe Marco D. Santambrogio – Ver. aggiornata al 9 Agosto 2013.
Fopndamenti di programmazione. 2 La classe String Una stringa è una sequenza di caratteri La classe String è utilizzata per memorizzare caratteri La classe.
Complessità di un algoritmo
Fondamenti di Informatica II Ingegneria Informatica / Automatica (A-I) Meccanica Prof. M.T. PAZIENZA a.a – 3° ciclo.
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 5 Le strutture informative Corso di Informatica 2 a.a. 2003/04 Lezione 5.
GLI ARRAY MONODIMENSIONALI. Utilizzando le nostre attuali conoscenze, proviamo a risolvere il seguente problema: Calcolare la media dei voti conseguiti.
1 FONDAMENTI DI INFORMATICA II Ingegneria Gestionale a.a ° Ciclo Puntatori e Stringhe.
Il linguaggio C Puntatori e dintorni.
1. 2 Variabili statiche e dinamiche Un programma è un processo in esecuzione a cui il sistema operativo assegna una certa zona di memoria. Tale zona può.
Informatica A.A. 2009/2010 Parte 4 Dai diagrammi di flusso alla programmazione strutturata: le istruzioni if, for, while, … Corso A: Prof. Stefano Berardi.
Fondamenti di Informatica II Ingegneria Informatica / Automatica (A-I) Meccanica Prof. M.T. PAZIENZA a.a – 3° ciclo.
Vettori (a una dimensione)
Parte 3 Lo stato: variabili, espressioni ed assegnazioni
Università di Torino – Facoltà di Scienze MFN Corso di Studi in Informatica Programmazione I - corso B a.a prof. Viviana Bono Blocco 7 – Array.
Fondamenti di Informatica 2 Ingegneria Informatica Docente: Giovanni Macchia a.a
Fondamenti di Informatica II Ingegneria Informatica (A-I) Prof. M.T. PAZIENZA a.a – 3° ciclo.
1 Il linguaggio C Puntatori e dintorni. 2 Puntatori : idea di base In C è possibile conoscere e denotare l’indirizzo della cella di memoria in cui è memorizzata.
Sottoprogrammi e funzioni
Ugo de'Liguoro - Informatica 2 a.a. 03/04 Lez. 7 Tipi di dato e strutture dati Specifica e realizzazione di strutture informative come classi.
© Piero Demichelis Tipi strutturati I tipi considerati finora hanno la caratteristica comune di non essere strutturati: ogni elemento è una singola entità.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Array e “stringhe” Marco D. Santambrogio – Ver. aggiornata al 8 Aprile 2015.
Laboratorio Informatico
Esercizi.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Array e “stringhe” Marco D. Santambrogio – Ver. aggiornata al 14 Marzo 204.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Il sistema di elaborazione Marco D. Santambrogio – Ver. aggiornata al 15 Maggio.
DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE Array e “stringhe” Marco D. Santambrogio – Ver. aggiornata al 28 Ottobre 2014.
30/10/01Array 1  Un array e’ una struttura dati che contiene piu’ valori del medesimo tipo.  La lunghezza di un array e’ stabilita quando l’array viene.
Basi di Java Strutture base di Java. Basi di java ▪Variabili ▪Operatori ▪Condizioni e Cicli ▪Array.
Transcript della presentazione:

Strutture 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

“Un 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

Indice 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).

1. 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.

Vettori (o “array”) in C++ Un vettore v (array) è una sequenza di n oggetti tutti del medesimo tipo, detti elementi del vettore, per qualche n>0 detto la dimensione del vettore. Gli elementi del vettore sono indicizzati utilizzando interi positivi, da 0 fino a n- 1, immaginati disposti come segue: v[0], v[1], … , v[n-1]

Notazioni 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 v[i..j] = {v[i], v[i+1], … , v[j]} Quindi, se i > j allora i..j = lista vuota; se un vettore v ha n elementi questi formano l’insieme v[0..n-1].

I vettori nella memoria RAM Gli elementi di un vettore occupano spazi di memoria consecutivi nella memoria RAM della macchina di Von Neumann. L’indirizzo &v[i] di ogni elemento si calcola dunque con la formula: &v[i] = &v[0] + i×d, dove d = sizeof (tipo di v[]) d = dimensione di ogni elemento (es.: 4 bytes) &v[i] = indirizzo v[i] = b+i×d v[i] indirizzo b+i*d v v[0] ind. b v[1] ind. b+d v[2] ind. b+2d b = indirizzo base = &v[0] =indirizzo v[0] (es.:byte n. 1 milione) i = indice di v[i] (detto anche “spiazzamento” ). Es.: i=100, indirizzo di v[i] = 1 000 400.

Vettori: dichiarazione Un vettore v in C++ è una costante di tipo indirizzo. Viene identificato con &v[0], l’indirizzo del suo primo elemento. La dichiarazione di v consiste nell’indicazione del nome e del tipo degli elementi del vettore, e nell’indicazione del loro numero, detto la dimensione del vettore. Con sizeof(v) si indica invece il numero di bytes occupati dal vettore: 100 = numero degli elementi, o dimensione di v Invece: sizeof(v) = 100*sizeof(int) = 400 int v[100]; Tipo degli elementi Nome del vettore: v vale &v[0]

Vettori: l’errore 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 w[dim]; /* 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 quest’ultimo caso un “crash” di programma */

Accesso 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, nell’assegnazione, 0 è un indice int v[100]; /* dichiarazione */ v[0] = 6; /* assegnazione, accesso in scrittura */ int n = v[0]; /* assegnazione, accesso in lettura */

Inizializzazione attraverso un ciclo Il programma che segue inizializza a 0 tutti gli elementi del vettore v: i dichiarato entro il for esisterà soltanto entro il for int v[100]; for (int i = 0; i < 100; i++) v[i] = 0; Nell’esecuzione del for, i assumerà tutti i valori tra 0 e 99 Prima dell’inizializzazione, tutti gli elementi di v, avendo un indirizzo di memoria, hanno comunque un valore, ma un valore “casuale”: provate a stamparli.

Inizializzazione 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 // a[0] = 22.2, a[1] = 44.4, a[2] = 66.6 Guardate l’esempio 6.3 del testo di Hubbard: spiega che la dimensione di a si calcola con la formula sizeof(a)/sizeof(double)

Attenti a non uscire dai limiti di un vettore Se un vettore ha dimensione d ed i<0, oppure i  d, allora v[i] non dovrebbe essere definito. Invece, in C/C++, v[i] esiste sempre, è per definizione il contenuto dell’indirizzo di memoria calcolato dalla formula: &v[0] + 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 v[i] con i<0, oppure i  d, sta a noi evitare che accada. Questo non accade in PASCAL: vedi Hubbard paragrafo 6.4

2. 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 v[100]; for (int i = 0; i < 100; i++) cout << v[i]; Se si scrive invece: cout << v; dato che v è identificato con l’indirizzo di v[0] si stampa l’indirizzo di v[0] (in esadecimale)

Somma 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 v[100]; double somma = 0; for (int i = 0; i < 100; i++) somma = somma + v[i]; double media = somma/100.0; /*dividiamo per un numero reale per evitare l’arrotondamento*/ Per tutte le sommatorie di cui non si conosca a priori nemmeno un termine, il valore iniziale dell’accumulatore è 0, l’elemento neutro della somma

Un test di uguaglianza errato Se applichiamo il test == a due vettori distinti otteniamo risposta costantemente uguale a false: int v[100], w[100]; 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 v==w vale sempre false. */

Un 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 un’iterazione. Ecco una soluzione con un WHILE: /* Il ciclo while trasporta il contatore i al primo indice per cui v[i]!=w[i], se ne esiste uno, altrimenti trasporta i fino ad N */ int i = 0; while (i < N && v[i] == w[i]) i++; if (i == N) cout << "v,w hanno elementi uguali per indici uguali"; else /* i < N */ cout << "v, w hanno diverso l’elemento di posto" << i;

Un 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 un’iterazione. Ecco la traduzione della soluzione precedente in un ciclo FOR: i è dichiarata fuori del for perché l’if che segue ne possa fare uso int i; for (i=0; i < N && v[i] == w[i]; i++){ }; //il corpo del FOR e’ vuoto if (i == N) cout << "v,w hanno uguali per indici uguali"; else cout << "v, w hanno diverso l’elemento di posto" << i;

Passaggio 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.:0<=n<=dim.a post.cond:sum(a,n) = somma di a[0..n-1]*/ {int i,s; for (i=0,s=0; i<n; i++) s=s+a[i]; return s;} int main(){ int a[] = { 11, 33, 55, 77 }; int size = sizeof(a)/sizeof(int); //num.el. di a cout <<"sum(a,size) ="<<sum(a,size)<<endl;}

Modifica 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 < dimensione di v // post: scambia v[i] con v[j] { int temp = v[i]; v[i] = v[j]; v[j] = temp; }

Inversione di un vettore v Idea. Poniamo due indici i, j agli estremi di v e muoviamo i, j uno verso l’altro. 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 v[0..n-1] in ordine inverso */ {for (int i = 0, int j=n-1; i<j; i++,j--) scambia(v, i, j);} Attenzione: se un vettore v è un parametro attuale di una funzione (per es. scambia) NON si scrive int v[] ma v i=0 i=1 j=n-2 j=n-1 v[0] v[1] … v[n-2] v[n-1]

3. 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 v[0], v[1], v[2], … in quest’ordine. bool Member (int n, int v[], int dim) /* pre: la dimensione di v e’ >= dim >=0. post: restituiamo true  esiste i tale che v[i]==n */ {bool trovato = false; /* “trovato” indica se n e’ gia’ stato trovato. “trovato” puo’ cambiare solo da false a true, mai viceversa.*/ for(int i=0;i<dim;i++){if(v[i]==n)trovato=true;} return trovato;}

Ricerca 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’ >= dim >=0. post: true sse esiste i t.c. v[i] == n*/ {bool trovato = false; //detta variabile “flag” for (int i=0; i<dim && !trovato == true; i++) if (v[i] == n) trovato = true; return trovato;}

Ricerca lineare con interruzione: seconda soluzione Possiamo eliminare la “flag” trovato, spostando (la negazione di) v[i] == 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’ >= dim >= 0 // post: true sse esiste i t.c. v[i] == n { int i; for (i=0; i < dim && v[i] != n; i++){}; return (i < dim);} Se i < dim è vero, il for è terminato perchè v[i] == n , altrimenti è terminato perchè il vettore è finito senza trovare n

Ricerca 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 v[i…j] di v in cui cercare Ad ogni passo confrontiamo n con il valore di indice medio in i..j, cioè m=(i+j)/2 Se v[m] != n allora cerchiamo in v[i..m-1] o in v[m+1..i] a seconda che n < v[m] oppure v[m] < n. Daremo ora una descrizione dettagliata del funzionamento della ricerca binaria, in 5 tappe.

Ricerca 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 l’indice del valore cercato (un i tale che v[i]=n, se esiste), altrimenti la lunghezza dim di v (dim sta per “non trovato”) 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 dim-1=19 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 7 dim-1=19 Valore cercato: n = 25. Indice del valore cercato: 7 ( v[7]=25)

Ricerca 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 v[i…j] di v tali che: se n è in v, allora n è in v[i…j]. v[i…j] 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 indice 0 i=3 indice 19 Cerco n=25 in v[i…j] j=16 Per es.: se n=25 è in v, allora n è in v[i..j] = v[3..16].

Ricerca binaria 3: il funzionamento 3. La ricerca binaria cerca un modo per avvicinarsi alla soluzione mantenendo vero l’invariante 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 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 dim-1= 19 Se il valore cercato è n = 43 allora l’ho trovato Punto intermedio m di i…j

Ricerca binaria 3: il funzionamento 3. La ricerca binaria cerca un modo per avvicinarsi alla soluzione mantenendo vero l’invariante Passo generico: dividiamo il sottovettore in due parti (quasi) uguali. Caso 2. Se il valore n cercato è < di quello nel punto intermedio, allora, dato che il vettore è ordinato, n si trova nella parte sinistra di v[i…j]. Spazio di ricerca 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 19 Punto intermedio m di i..j Valore cercato: n = 25

Ricerca binaria 3: il funzionamento 3. La ricerca binaria cerca un modo per avvicinarsi alla soluzione mantenendo vero l’invariante Passo generico: dividiamo il sottovettore in due parti (quasi) uguali. Caso 3. Se il valore cercato n è > di quello nel punto intermedio, allora n si trova nella parte destra di v[i…j] . Spazio di ricerca 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 19 Valore cercato: n = 60 Punto intermedio m di i..j

Ricerca 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 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 19 Valore cercato (e trovato): n = 43 Punto intermedio di indice m

Ricerca 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 v[i…j] con i>j) 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 19 n=23 dovrebbe essere qui in mezzo, tra 21 e 25: ma questo intervallo è vuoto. Il valore n non viene trovato. Valore cercato (e non trovato): n=23

Ricerca binaria 5: l’inizio della computazione 5. Definiamo le condizioni iniziali per la ricerca binaria All’inizio, il segmento v[i…j] di vettore in cui cercare n e’ l’intero vettore n. Spazio di ricerca iniziale: v[0..n-1] 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 19

Ricerca binaria: i dettagli della codifica dei dati Stabiliamo ora i dettagli della codifica del segmento di vettore V[i..j] che usiamo durante la ricerca. Il sottovettore V[i..j], a cui limitiamo la ricerca, è compreso tra le posizioni i e j incluse Spazio di ricerca in un passo generico della computazione 3 5 6 13 18 21 21 25 36 43 49 51 53 60 72 74 83 88 91 95 i m j 19 Il punto medio m ha indice: (i + j) diviso 2 Se i > j allora il sottovettore V[i..j] è vuoto

Ricerca binaria: l’implementazione Scriviamo ora una funzione C++ che implementa la ricerca binaria, usando pre- e post-condizioni e l’invariante come commenti. La funzione ottenuta è decisamente breve rispetto a tutta la discussione che è servita a presentarla.

Ricerca binaria: l’implementazione int binsearch (int n, int v[], int dim) // pre: la dim. di v e’ >= dim e v[0..dim-1] è ordinato // post: i tale che v[i] == n se ne esiste uno, altrimenti: dim { int i = 0, j = dim - 1, m; while (i <= j) //inv. se n in v[0..n-1] allora n in V[i..j] {m = (i+j)/2; if (v[m] == n) return m; else if (n < v[m]) j = m - 1; else /* (v[m] < n) */ i = m + 1;} return dim; // dim sta per “non trovato” }

4. 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 A[10][20]; // matrice 10righe x 20colonne di double A[i][j] = 7.23; // se 0  i < 10 e 0  j < 20 allora // scrive 7.23 come valore della // i-esima riga e j-esima colonna di A

Passaggio 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 l’indicazione della lunghezza di V dentro int V[]: la lunghezza n del vettore è a sua volta un parametro, che può cambiare da una chiamata all’altra. 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 [10][20] ) {…}

Passaggio 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 A[10][20] e double B[20][20] sono due tipi diversi. La seconda dimensione di A, ovvero il numero 20 delle colonne di A, è purtroppo necessaria per ricostruire l’indirizzo della variabile A[i][j] nella macchina di Von Neumann, e non è ricostruibile a partire dalla sola variabile A. void g(double A [10][20] ) {…}

Un esempio di uso di matrici: il triangolo di Tartaglia Scriveremo ora una funzione che assegna le prime n+1 righe del triangolo diTartaglia a una matrice (n+1)x(n+1). Per definizione, ogni elemento posto ai lati del triangolo di Tartaglia vale 1, e ogni altro elemento è la somma dell’elemento posto sopra e di quello posto sopra e a destra. Niccolò Tartaglia 1499-1557

Un esempio di uso di matrici: il 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 = A[0][0] = A[1,0] = A[2][0] = … =A[i,0] = … e 1 = A[0][0] = A[1,1] = A[2][2] = … = A[i,i] = … e per 0<j<i: A[i][j] = A[i- 1][j-1]+A[i-1][j]. 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 1 2 3 4 5 6 Per es.: 6 = A[4][2] = A[3][1] + A[3][2] = 3 + 3.

Il triangolo di Tartaglia usando una matrice void Tartaglia(int n) // stampa righe da 0 a n del triangolo di Tartaglia {int a[n+1][n+1]; //definisce matrice (n+1)x(n+1) for (int i = 0; i<=n; ++i) // costruzione “riga per riga” {a[i][0] = 1; // assegna 1 a tutta la colonna 0 for (int j = 1; j<i; ++j) // assegna colonne da 1 a i-1 a[i][j] = a[i-1][j-1] + a[i-1][j]; a[i][i] = 1; // assegna 1 a tutta la diagonale} // stampa righe da 0 a n del triangolo …(vedi pagina seguente per sapere come) … }

Stampa 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 <= n; ++i) { for (int j = 0; j <= i; ++j) cout << setw(4) << a[i][j]; cout << endl;} } L’istruzione setw(4) esegue la prossima stampa con almeno 4 spazi. Richiede di includere la libreria: #include <iomanip.h>

5. 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 <nome struttura> { <tipo1> <etichetta campo1>; ... <tipok> <etichetta campok>;} Come concetto matematico, un record corrisponde a un prodotto cartesiano: tipo1 x … x tipon mentre un vettore corrisponde a un insieme potenza: tipon

I 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 l’uso dei records.

Frazioni rappresentate da un record Poiché qui i due campi hanno lo stesso tipo avremmo potuto scrivere: int num, den; Ratio si aggiunge ai tipi definiti nel programma 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

Record 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); // a=2/3 }

Record 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

Vettori parzialmente riempiti Di un vettore occorre ricordare la dimensione; se poi se ne usa solo una parte, come abbiamo fatto nell’esercizio 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.

Vettori 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 v[10]; int prox_libero; }; /* prox_libero = indice del prossimo indirizzo libero */

Funzioni 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 < a.prox_libero; i++) cout << a.v[i] << " "; cout << endl;}

Funzioni 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 < 10, aggiunge n in ultima pos. e restituisce true; restituisce false altrimenti */ {if (a.prox_libero < 10) {a.v[a.prox_libero] = n; a.prox_libero++; return true;} else return false;}

Riepilogo 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.