Algoritmi esponenziali
• Supponiamo che f(n) sia la funzione che
rappresenta il numero di operazioni eseguite
da un algoritmo e supponiamo che il tempo
necessario per compiere una operazione sia un
microsecondo: 1µs = 10-6sec.
• Vogliamo vedere che per valori di n non
elevati gli algoritmi impiegano un tempo
troppo elevato per poter essere utilizzati: gli
algoritmi esponenziali sono impraticabili.
Algoritmi
esponenziali
Algoritmi esponenziali
• Vediamo questa tabella dove riportiamo, al
variare di n, il tempo impiegato da alcune
funzioni di n
n
10*n2
n3
log2(n)
100*n
10
2.3 µs
1ms
1ms
1ms
20
2.99µs
2ms
4ms
8ms
60
4.09µs
6ms
36ms
0.21sec
2n
Algoritmi esponenziali
260µs ~ 366 secoli, vediamo come mai
260µs ~ 10 60×0.3µs ~ 1018µs~ 1012 sec (1.1529 ×1012)
(1µs = 10-6sec )
1024µs
~ 1ms
1.048sec
260µs
~ 366 secoli
• Quanti secondi in un anno?
1 minuto = 60 secondi
1 ora = 3600 secondi
1 giorno = 86400 secondi 1 secolo = 3.15×109 secondi
secondi
1.1529 ×1012
secoli =
=
= 366
secondi in un secolo
3.15×109
1
Algoritmi esponenziali
• Esistono degli algoritmi che sono intrinsecamente
esponenziali:
• calcolare le permutazioni di n elementi:
n! ~ nn
• Ci sono problemi che sono risolti da un algoritmo
deterministico esponenziale, ma il cui algoritmo
non deterministico è polinomiale. L’algoritmo non
deterministico è un algoritmo ideale che può essere
simulato pensando di poter eseguire scelte
contemporanee; è rappresentabile su un albero: la
profondità dell’albero può essere proporzionale alla
dimensione del problema. Tali problemi si chiamano
NP (Non deterministico Polinomiale).
Algoritmi esponenziali
• Esempio.
• Consideriamo la formula logica
F(x1, x2, x3) = (x1 e x2 e x3) o (non x1 e non x2)
e ci chiediamo se esiste una scelta di valori per
le variabili x1, x2, x3 che renda vera F.
• Un algoritmo deterministico prova tutte le
possibilità e poiché il valore di xi può essere
vero o falso, le possibilità sono 23.
Algoritmi esponenziali
Algoritmi esponenziali
• In generale considerando F(x1, x2, .., xn) il
problema può essere risolto in un tempo O(2n).
• Se potessimo esplorare l’albero in modo da poter
percorrere contemporaneamente i due rami V e F, in
n passi arriveremmo alle foglie. Quindi l’algoritmo
non deterministico è O(n).
• L’algoritmo non deterministico è in grado di
scegliere il valore di xi che porta alla
soluzione. Per simulare tale comportamento si
può costruire un albero: ogni nodo rappresenta
una variabile della formula; da ogni nodo
partono due rami che rappresentano il vero e il
falso.
v
x1
x2
x3
……
x1
f
x2
x3
x2
x3
x3
x3
..............
2
Complessità asintotica
• Le considerazioni fatte sulla complessità
valgono solo se
n →∞
e F(n) →∞
• Se invece n si mantiene limitato anche un
algoritmo esponenziale può essere utilizzato;
per lo stesso motivo anche la costante
moltiplicativa, che solitamente trascuriamo
nella notazione O-grande, può invece essere
fondamentale nella scelta.
Complessità asintotica
• Esempio.
f1(n) = 1000 * n
f1 è O(n)
f2(n) = 10 * n2
f2 è O(n2)
• Quindi:
• se n →∞
è preferibile f1
• se n ≤10
è preferibile f2 , infatti:
1000 * n ≤ 1000*10 = 104
10 * n2 ≤ 10*100 = 103
Ordinamento per inserimento
Ordinamento per
inserimento
• L’idea è quella di inserire una componente in
ordine rispetto ad una sequenza di componenti
già ordinate.
• Esempio. Vogliamo inserire 6 nella sequenza:
2 5 7 9 10
• La cosa più efficiente è partire dall’ultima posizione e
slittare verso destra le componenti che sono maggiori
si 6: in tale modo quelle più piccole restano “ferme”:
7
7
9 10
2 5
ora c’è posto per inserire 6.
3
Ordinamento per inserimento
• Per effettuare l’ordinamento si parte dalla seconda
componente che si inserisce in ordine rispetto alla
prima, poi si considera la terza, che si inserisce in
ordine rispetto alle prime due, in generale: si vuole
inserire la k-esima componente in ordine rispetto alle
k-1 già ordinate.
• Poiché il numero di componenti non aumenta, per
poter slittare in avanti le componenti che precedono
nella sequenza, è necessaria una variabile di appoggio
x per salvare il valore:
2 5 9 11 3 1 20
2 3
5
9
x
11 1
20
Ordinamento per inserimento
• Bisogna non uscire dall’array se si deve inserire un
valore prima della prima componente. Ci sono varie
strategie per realizzare ciò, una di queste è sfruttare la
“valutazione pigra dei predicati”:
……………
for(k=1; k<=n-1; k++) {//v[0] è già
//in ordine
x=v[k];
i=k;
while((i!=0) && (x < v[i-1])){
v[i]=v[i-1];
i--; }
//fine while
v[i]=x;
}//fine for
Ordinamento per inserimento
• Complessità.
• Caso favorevole: array già ordinato
1 3 8 10 20
il predicato del ciclo interno è sempre falso, ( x <
v[i-1] è falso), quindi il numero di operazioni è
proporzionale a n : Ω (n).
• Caso peggiore: array in ordine inverso
20 10 9 7 4 1
il ciclo interno viene sempre eseguito per valori
crescenti di i=k:
1 + 2 + 3 + …. + (n-1) = n (n-1)/2 : O(n2/2)
Divide et impera
4
Divide et impera
Divide et impera
• La tecnica detta “divide et impera” è una
strategia generale per impostare algoritmi.
• Consideriamo un problema P e sia n la
dimensione dei dati, la strategia consiste nel:
• suddividere il problema in k sottoproblemi Pi di
dimensione inferiore (ciascuno di dimensione ni) e
successivamente riunire i risultati ottenuti dalle k
soluzioni.
• La frase è attribuita a Filippo il Macedone e fu un
principio politico: mantenere divise le popolazioni
dominate per poter governare con più facilità.
• Se i k sottoproblemi sono “formalmente” simili al
problema di partenza, si ottiene una scomposizione
ricorsiva. Ci deve pertanto essere una dimensione h
del problema che porti ad una risoluzione diretta, vale
a dire che non necessiti della ricorsione.
• Indichiamo con:
• S l’insieme dei dati
• k il numero dei sottoproblemi
• h la dimensione limite
• Si può scrivere uno schema generale per la
scomposizione.
Divide et impera
Divide et impera: complessità
algoritmo DIVETIMP (S, n)
se n < h
allora risolvere direttamente il problema P
altrimenti
dividere S in k sottoinsiemi
risolvere separatamente i k
sottoproblemi P1, …, Pk:
DIVETIMP(S1,n1), … ,
DIVETIMP(Sk,nk)
riunire i risultati ottenuti
//finese
//fine algoritmo
• Indichiamo con T(n) la complessità del
problema P sull’insieme dei dati di dimensione
n; poiché l’algoritmo è ricorsivo si ottengono
delle formule “di ricorrenza”:
T(n) = costante
n<h
T(n) = D(n) + C(n) + T(n1) + T(n2) + … T(nk)
• D(n): complessità dell’algoritmo per dividere
l’insieme
• C(n):
complessità dell’algoritmo per riunire i
risultati
• T(ni): complessità dell’algoritmo sull’insieme di
dimensione ni.
5
Quicksort
• L’idea è la seguente:
• è più conveniente ordinare due array di s e t
componenti piuttosto che un array di n
componenti (s + t = n)
• si aumenta l’efficienza dell’ordinamento
scambiando elementi lontani tra loro.
(Argomenti avanzati 13.3)
• Verifichiamo la prima idea; supponiamo s=t=n/2 e
prendiamo la formula n(n-1)/2 che rappresenta la
complessità dell’ordinamento nel caso peggiore e
riscriviamola per n/2 invece che n.
Quicksort
2·[(n/2) (n/2 – 1) /2] = n2 /4 – n/2
n2 /4 – n/2 < n2 /2 – n/2 = (n2 – n)/2 =
= n ·(n-1) /2
• Verifichiamo la seconda idea; supponiamo di
avere un array ordinato in senso inverso;
scambiando gli elementi opposti (il primo con
l’ultimo, il secondo con il penultimo, ecc.) in
n/2 operazioni ordiniamo l’array. Questi
elementi sono “lontani” tra loro: quelli più
grandi sono al posto di quelli più piccoli.
Quicksort
Quicksort
• Dobbiamo ora dividere l’insieme in due parti e
vogliamo sfruttare la seconda idea: scambiare
elementi lontani. Scegliamo un elemento dell’array
per eseguire i confronti e lo chiamiamo conf;
dividiamo l’insieme in modo tale che gli elementi più
piccoli di conf possano essere messi a sinistra di conf
e quelli più grandi a destra, rispettando così l’ordine.
A questo punto l’insieme è diviso in due parti
indipendenti e l’elemento conf è al suo posto.
• Si potrà proseguire in maniera ricorsiva fino a
considerare un array di dimensione 1, che non dovrà
essere ulteriormente suddiviso. Se “guardiamo”
(riunire i risultati) l’array dalla prima componente
all’ultima, vediamo che l’array è ordinato.
≤
conf
≥
• Scriviamo il progetto dell’algoritmo secondo lo
schema “divide et impera” per ordinare un array v
dalla componente n1 alla componente n2 (alla prima
invocazione del metodo n1 ed n2 saranno la prima e
ultima componente).
6
Quicksort
Quicksort
algoritmo quicksort(n1,n2,v)
se n1 < n2
allora chiamare l’algoritmo
partizione(n1, n2, v) che restituisce
il valore k della posizione di conf
chiamare quicksort(n1, k-1, v)
chiamare quicksort(k+1, n2, v)
//finese
//fine quicksort
• Come scegliere l’elemento conf?
• Dobbiamo stabilire un criterio che si possa
facilmente ripetere in tutte le suddivisioni
successive.
• Stabiliamo di scegliere la prima componente di
quella porzione di array (da n1 a n2) che
vogliamo ordinare: v[n1].
• Quando n1=n2 l’array ha un solo elemento e pertanto la
ricorsione ha termine.
• Ci sono varie scritture dell’algoritmo quicksort,
alcune ottimizzano il numero di confronti, ma
lasciano inalterata la complessità.
Quicksort
Quicksort
• Per realizzare la partizione avremo bisogno due
indici: un indice i che scorre l’array con valori
crescenti e che parte dalla posizione successiva a
quella di conf (i=n1+1), un altro indice k che
descrive l’array con valori decrescenti e parte
dall’ultima posizione (k=n2).
• Quando questi due indici saranno uguali avremo
terminato la partizione e si potrà “sistemare” conf al
suo posto.
• Vediamo un progetto per l’algoritmo di partizione.
algoritmo partizione(n1, n2, v)
conf ← v[n1]
i ← n1+1
k ← n2
mentre i ≠ k eseguire
menre v[i] ≤conf e i ≠ k eseguire
i ← i+1
//fine mentre
mentre v[k] ≥conf e i ≠ k eseguire
k ← k-1
//fine mentre
scambiare v[i] con v[k]
7
Quicksort
//fine mentre: ciclo esterno
//sistemare conf nella posizione
se v[k] > conf
allora k ←k-1
Quicksort
k=i: è al suo posto
• Esempio.
10 5 11
conf
//finese
scambiare v[n1] con v[k]
//fine partizione
10
5
Quicksort
10
2
20
k=6 k=7
1
13 11 20
i = k = 5 termina anche il ciclo esterno
v[k]>conf (10>13) quindi k-1
1
conf
13 2
i=4 i=5
k=5
• E necessario il confronto tra conf e v[k]?
• Esempio.
20 3 1
1
i=2 i=3
9
7
5
2
10
13 11 20
Quicksort
• L’algoritmo del libro è scritto diversamente:
from=n1
to=n2
11
i=2 i=3 i=4 i=5 i=6 i=7= k
i = k = 7 termina anche il ciclo esterno
v[k]>conf falso :
k non varia
11
3
1
10
9
7
20
i=n1-1 k=n2+1 (esterni), conf=pivot
il ciclo esterno ha predicato i<k ; all’inizio del ciclo
i++ ; il confronto v[i]<conf è falso (sono uguali)
quindi si passa al secondo ciclo e conf viene messo
come ultimo (non necessariamente al suo posto).
Manca il confronto i≠k, perché in fondo c’è conf.
Nella versione con conf al centro è necessario il
predicato i ≠ k per non uscire dall’array (se conf è il
più grande elemento, come nel secondo esempio).
8
Quicksort
Quicksort
• Complessità. Contiamo i confronti tra conf e
gli elementi v[i]:
0
se n = 0,1 (n1<n2)
T(n) =
D(n) + C(n) + T(k-1) + T(n-k)
D(n) = complessità dell’algoritmo partizione
C(n) = 0 “guardare” le due parti dell’array
D(n) è O(n): n-1 confronti nei predicati dei
cicli interni + 1 confronto per sistemare conf.
• Caso peggiore: vettore ordinato, la partizione è
sbilanciata:
Quicksort
Mergesort
• Caso favorevole: conf sempre al centro: la
partizione è bilanciata:
• L’idea è la seguente:
• dividere l’insieme in due parti uguali di n/2
componenti
T(n) = n + T(n/2) + T(n/2) = n + 2T(n/2) =
= n + 2(n/2 + 2T(n/4)) = 2n + 22T(n/22) =
= ….. = k·n + 2k·T(n/2k)
se n = 2k allora k = log2n
T(n) = n + T(0) + T(n-1) = n + T(n-1) =
= n + (n-1 + T(0) + T(n-2)) = n + n-1 + T(n-2) =
= n + (n-1) + (n-2) + T(n-3) =
= ….. = n + (n-1) + + 1 + T(0) + T(n – (n-1)) =
= n (n-1)/2
n/2
n/2
• se fossero già ordinate le potremmo riunire
con un algoritmo di fusione (merge)
O(n·log2n)
9
Mergesort
Mergesort
Esempio. Consideriamo i due array ordinati A e B e
costruiamo l’array MG che contiene gli elementi di A
e B (con eventuali ripetizioni) in ordine: si
considerano le prime componenti, quella più piccola
viene inserita nell’array MG, e si considera la sua
successiva; quando uno dei due è terminato, basta
ricopiare l’altro.
A:
1
5
6
8
10
B:
0
1
3
4
MG:
0
1
1
3
4
5
6
8
• Per ordinare le due parti di n/2 componenti,
usiamo in maniera ricorsiva la stessa strategia:
dividere a metà, per poi fondere le parti
ordinate, proseguendo fino ad un array di un
solo elemento.
• La dimensione limite è h=2: se n<2 c’è un solo
elemento.
• Vediamo quindi il progetto dell’algoritmo
mergesort per ordinare un array v dalla
componente p alla componente q (par.13.4).
10
Mergesort
algoritmo mergesort(v, p, q)
se p<q
allora
medio ← (p+q)/2
//troncata
chiamare mergesort(v, p, medio)
chiamare mergesort(v, medio+1, q)
chiamare merge(v, p, medio, q)
//finese
//finemergesort
• Per gestire la fusione pensiamo all’array diviso in due
parti, da p a medio, e da medio+1 a q; inoltre usiamo
un array di supporto s per “appoggiare” le componenti
di v in ordine.
Mergesort
algoritmo merge(v,p,medio,q)
h←p
i←p
k ← medio+1
mentre h ≤ medio e k ≤ q eseguire
se v[h] ≤ v[k]
allora
s[i] ← v[h]
h ← h+1
altrimenti s[i] ← v[k]
k ← k+1
//finese
i ← i+1
10
Mergesort
Mergesort
//finementre
//ricopiare la parte di array non esaminata
se h = medio+1
allora copiare in s la seconda parte
altrimenti copiare in s la prima parte
//finese
ricopiare s sul v
//fine merge
• Esercizio.
Implementare
gli
algoritmi
quicksort e mergesort ed eseguire le prove dei
tempi o contare le chiamte ricorsive.
• Complessità. Contiamo i confronti tra gli
elementi dell’array:
0
se n = 0,1
T(n) =
D(n) + C(n) + T(n/2) + T(n/2)
D(n) = 0
calcolo di medio
C(n) = complessità dell’algoritmo di fusione
C(n) è O(n): vengono considerati tutti gli
elementi delle due parti lunghe n/2
Mergesort
Mergesort e Quicksort
• Il numero di confronti è sempre lo stesso perché
anche se l’array è ordinato si esegue sempre la
divisione a metà e la fusione delle due parti; le
partizioni sono bilanciate:
• Confrontiamo i due algoritmi.
• L’algoritmo mergesort ha la complessità più
bassa nel caso peggiore O(nlog2n); esegue però
molte ricopiature per eseguire la fusione.
• L’algoritmo quicksort ha caso peggiore
O(n2/2), ma nel caso favorevole e medio è
O(nlog2n).
• In Java l’algoritmo sort implementa
l’ordinamento per inserimento per n<7, e
quicksort negli altri casi.
T(n) = n + T(n/2) + T(n/2) = n + 2T(n/2) =
= n + 2(n/2 + 2T(n/4)) = 2n + 22T(n/22) =
= ….. = k·n + 2k·T(n/2k)
se n = 2k allora k = log2n
O(n·log2n)
11
Mergesort
Il meccanismo della ricorsione
• Albero delle chiamate ricorsive.
v0 v1 v2 v3 v4 v5 v6 v7
1 0 8 5 4 3 -1 9
0158
1 0 8 5
4 3 -1 9
01
58
1 0
1
0
8 5
8
4 3
5
4
3
-1 9
-1
9
Il meccanismo della ricorsione
• Schematizziamo un algoritmo ricorsivo nel modo
seguente:
algRicorsivo(parametri)
α
se P
allora β
altrimenti γ
chiama algRicorsivo(nuoviparametri)
δ
//finese
ritorno
//fine algRicorsivo
Il meccanismo della ricorsione
dove α, β , γ , δ sono gruppi di istruzioni, P è il
predicato che governa la ricorsione, ritorno indica
ritorno al chiamante (ci può essere uno scalare oppure
void).
• Vediamo il funzionamento:
• α (P vero) β ritorno
• α (P falso) γ chiama [α (P vero) β ritorno] δ
ritorno
• Le istruzioni δ devono essere eseguite con i valori dei
parametri al momento in cui le operazioni α γ hanno
effettuato la chiamata: vengono salvati nel
RunTimeStack.
• Supponiamo che P sia falso 3 volte e indichiamo solo
le istruzioni α, β , γ , δ
• Dallo schema si vede che una scomposizione
ricorsiva si può sempre trasformare in iterativa.
falso
falso
falso
vero
α γ
α γ
αγ
α β
ritorno ritorno
δ
δ
ritorno
δ
12
Il meccanismo della ricorsione
• Se l'istruzione δ non c'è, la ricorsione si scioglie
facilmente:
α (P falso) γ
α (P falso) γ
α (P falso) γ
α (P vero) β
e si trasforma nella seguente struttura iterativa dove
(non P) è il valore di predicato che effettua la
chiamata:
α
mentre non P eseguire
γ
costruire nuoviparametri
α
//finementre
β
Il meccanismo della ricorsione
• Esempio. Stampare i primi numeri naturali in ordine
decrescente: n, n-1, n-2, …, 1.
algoritmo stamparic( intero n)
se n>0
allora stampare n
chiamare stamparic(n-1)
//finese
//fine algoritmo
• manca δ; l’algoritmo è semplice, manca l’istruzione
manca β pertanto la chiamata è per n>0.
α, e
Il meccanismo della ricorsione
• Trasformiamo l’algoritmo: se la chiamata
viene eseguita quando n>0, pertanto questo
sarà il predicato del ciclo:
mentre n>0 eseguire
stampare n
n← n-1
//costruire nuovi parametri
//finementre
Matrici: array a due
dimensioni
• Esercizio. Stampare i numeri in ordine crescente.
Costruire “manualmente” lo Stack per memorizzare
δ.
13
Matrici
Matrici
• Vogliamo
rappresentare
informazioni
omogenee (dello stesso tipo) ma descritte da
una coppia di indici (Argomenti avanzati 7.2):
aik
i=1, 2, …, n
• Molti problemi matematici fanno uso di
matrici; uno dei problemi più frequenti è la
risoluzione di un sistema lineare di n
equazioni in n incognite (estensione del
sistema 2×
×2 che rappresenta il determinare il
punto di intersezione di due rette del piano) e
che si può rappresentare algebricamente come
“prodotto matrice × vettore”:
Ax=b
k=1, 2, … , m
matrice A di n righe e m colonne:
a11 a12 … a1m
A=
………….
an1 an2 …. anm
Matrici
• Come si definiscono le matrici in Java.
• Una matrice è un array di array:
int a[][];
// a è la referenza
per creare l’oggetto si deve usare new:
a = new int [5][4];
• La matrice a ha le seguenti componenti:
a[0][0]
a[1][0]
.
a[4][0]
.
.
.
.
. . a[0][3]
. . a[1][3]
.
. . a[4][3]
Matrici
• Per accedere ad un elemento di un array
bidimensionale si devono indicare entrambi gli
indici:
a[2][1] = 5;
e tali indici devono essere compatibili con la
dimensioni dell’array. Nel nostro esempio le
righe sono 5 e quindi l’indice varia da 0 a 4, le
colonne sono 4 e l’indice delle colonne varia
da 0 a 3.
14
Matrici
Matrici
• Per conoscere il valore delle due dimensioni si
usa il campo length:
• il numero di righe è
a.length
• il numero di colonne è a[0].length
• Cosa è a[0]? Cosa “vede” a?
• a[0] è una referenza ad un array di interi che
rappresenta la prima riga;
• a è una referenza ad un array di referenze (o come
si dice: un array bidimensionale è un array di
array).
a[0]
a[1]
a[2]
a[3]
a[4]
a[0][0] … a[0][3]
a
a[4][0] … a[4][3]
Matrici
• Se n=m la matrice si dice quadrata. Queste sono le
matrici maggiormente usate nella matematica.
• Gli elementi con indice uguale individuano un array
che si chiama diagonale della matrice:
diagA = [a11, a22, …, ann]
• Se due matrici A e B hanno lo stesso numero di righe
e colonne si può costruire la matrice somma C:
C=A+B
cik = aik + bik
Matrici
• Date due matrici A (n × p) e B (p × m) se il
numero di colonne di A è uguale al numero di
righe di B allora si può definire la matrice C =
A × B “prodotto righe × colonne”:
p
cik =
∑
ait · btk
t =1
• Consideriamo matrici quadrate.
• La matrice A possiede n2 elementi, quindi
difficilmente gli algoritmi su matrici avranno
una complessità inferiore a O(n2).
15
Matrici
Matrici
• L’algoritmo per costruire la somma di due matrici è
Θ(n2).
• L’algoritmo per costruire il prodotto, applicando la
definizione, è Θ(n3); ne esistono di complessità O(nα)
con 2< α < 3.
• L’algoritmo per risolvere il sistema lineare Ax=b
(data la matrice A e il vettore b determinare il vettore
x) noto come “regola di Cramer” ha complessità
O(n!); esistono “metodi diretti” con i quali si
trasforma la matrice in una equivalente, per il
problema di risolvere il sistema lineare, e che
richiedono O(n3) operazioni e “metodi iterativi” che
approssimano la soluzione in O(n2) operazioni.
• L’identità del prodotto tra matrici si chiama
“matrice identica” I ed è una matrice con 1
sulla diagonale e 0 altrove.
Matrici
Matrici
• Il prodotto tra matrici non è commutativo:
A×B ≠ B×A
fatta eccezione del caso in cui una delle due sia
la matrice I oppure che le due matrici siano
uguali.
• La matrice costruita a partire dalla matrice A
scambiando le righe con le colonne si chiama
“matrice trasposta” di A, e si indica con AT:
aTik = aki
• Esercizio. Scrivere un algoritmo per costruire la
matrice identica che esegua n2 + n assegnazioni
• non eseguire l’ovvio controllo:
se i ≠k
allora ….0
altrimenti … 1
che porterebbe a n2 assegnazioni + n2 confronti
• Una matrice si dice simmetrica se:
aik = aki
∀ i,k
• Se A = AT allora A è simmetrica.
• Due matrici A e B sono uguali se:
aik = bik
∀ i,k
• Esercizio. Scrivere un algoritmo (efficiente) per
verificare che due matrici sono uguali o che una
matrice è simmetrica.
• Suggerimento: se una proprietà deve essere verificata per
ogni elemento, essa è falsa se esiste un solo elemento che
non la verifica.
16
Matrici
Matrici
• Esercizi per imparare ad usare gli indici delle
matrici.
1) date due matrici A(n × m) e B(p × q)
verificare se B è contenuta in A
1 2 3 5
A = 4 0 -1 7
3 2 1 4
invece
1 2
3 2
0
-1
2
1
B=
2) Dato un cruciverba A(n×
×m), matrice di
caratteri, e una parola P composta da q
caratteri verificare se la parola P sta nel
cruciverba per righe o per colonne; verificare
che le dimensioni siano compatibili, cercare la
prima componente di P se è presente in A ed
eseguire la verifica solo se la lunghezza della
parola non supera il numero di righe o di
colonne che restano da verificare.
non è contenuta
Matrici
3) Dati n numeri a1, a2, a3, …, an determinare
quanti sono minori a1, quanti minori di a2, di
a3, … e in quali posizioni si trovano (per ogni i
si deve poter memorizzare un array di indici) e
quali sono (per ogni i si deve poter
memorizzare un array di valori) .
Altri algoritmi per
Fibonacci
17
Altri algoritmi per Fibonacci
Altri algoritmi per Fibonacci
• I due seguenti algoritmi si basano sulle matrici.
• Consideriamo la matrice A e la matrice An-1:
• Esercizio. Verificare la formula per n=2, 3, 4.
• Si può allora costruire un algoritmo che calcola la
potenza n-esima di A, matrice M, e restituisce il
primo elemento di M, corrispondente a Fn:
si può dimostrare per induzione
che la matrice An-1 = A × A × .. ×A
1 1
A=
n-1 volte
1 0
1 1
An-1 =
n-1
Fn
Fn-1
=
1 0
Fn-1 Fn-2
Altri algoritmi per Fibonacci
• La complessità di tempo è O(n): infatti c’è
una struttura iterativa per calcolare il prodotto
tra due matrici di dimensione 2×2 (un numero
finito di prodotti e somme)
• La complessità di spazio è O(1): le matrici A,
M, I occupano una quantità di spazio costante.
• Il prossimo algoritmo è una ottimizzazione
dell’algoritmo precedente: si può eseguire la
potenza n-esima con un algoritmo basato sui
quadrati.
intestazione metodo fibonacci4 (n intero)
intero i
M ← I //matrice identità
per i da 1 a n-1 eseguire
M←M*A
//fineper
restituire M[0][0]
//finealgoritmo
Altri algoritmi per Fibonacci
• Esempio. Vogliamo calcolare 48.
48 = 4 · 4 · …. · 4
8
42 = 16 162 = 44 = 256
2562 = 48
quindi in 3= log28 passi abbiamo eseguito il
calcolo.
• In generale:
Mn = ( M n/2)2
con n pari
18
Altri algoritmi per Fibonacci
poiché la divisione è troncata, se n è dispari
(n/2) · 2 è uguale a
((n-1)/2) · 2 = (n-1)
occorre perciò un’altra moltiplicazione per M.
intestazione metodo fibonacci5 (n intero)
M←I
chiama potenzamatrice(M, n-1)
restituire M[0][0]
//finealgoritmo
Altri algoritmi per Fibonacci
intestazione algoritmo potenzamatrice(matrice M, intero n)
se n>1
allora potenzamatrice(M, n/2)
M ← M*M
//finese
se n è dispari
allora M ← M * A
//finese
//finealgoritmo
• La complessità di tempo è
• La complessità di spazio è
O(log2n)
O(1).
Trasformare array paralleli in
array di oggetti
Trasformare array
paralleli in array di
oggetti
• Un array è una struttura di dati omogenea: gli
elementi dell’array sono tutti dello stesso tipo
(che è il tipo dell’array).
• A volte è necessario gestire informazioni di
tipo diverso ma riferite allo stesso concetto.
• Supponiamo di voler memorizzare delle
informazioni riguardanti gli impiegati di una
ditta: nome, stipendio, età. Possiamo pensare
di costruire tre array distinti, uno per ogni tipo
di informazione (Consigli per la qualità 7.2).
19
Trasformare array paralleli in
array di oggetti
Trasformare array paralleli in
array di oggetti
Per avere informazioni
nome stipendio età
sull’impiegato i-esimo
accediamo alla componente
i-esima di ciascuno dei tre array.
• I tre array sono strettamente correlati tra loro: devono
avere la stessa lunghezza, un algoritmo che elabora le
informazioni su un impiegato deve avere i tre array
tra i suoi parametri, se si volesse aggiungere
un’ulteriore informazione, si dovrebbe tenere
presente l’organizzazione comune ai tre array (l’iesimo impiegato sta all’i-esimo posto).
• Linguaggi come Java mettono a disposizione la
possibilità di considerare l’impiegato come un
concetto e di considerarlo un’unica informazione
suddivisa in tre campi.
Trasformare array paralleli in
array di oggetti
Trasformare array paralleli in
array di oggetti
• Questo tipo di informazione nei linguaggi di
programmazione si chiama record e
rappresenta una collezione di elementi di tipo
diverso. La parola record (registrazione) è una
parola
“antica”
dei
linguaggi
di
programmazione, così come la parola file
(archivio). Sono parole nate in riferimento alla
registrazione di dati su supporti fisici come i
nastri magnetici (o i dischi); l’archivio che
contiene l’insieme delle informazioni registrate
prendeva il nome di file di dati.
• Noi possiamo realizzare un concetto
“impiegato” ed utilizzare una classe, i cui
campi
saranno
le
informazioni
che
caratterizzano l’impiegato:
String nome[] = new String[1000];
double stipendio[]=
new double[1000];
int eta[]= new int[100];
i
public class Impiegato{
String nome;
double stipendio;
int eta;
}
20
Trasformare array paralleli in
array di oggetti
• Possiamo costruire un array ditta le cui
componenti sono di tipo Impiegato:
Impiegato ditta[] =
new Impiegato[100];
• In tale modo invece di avere tre array paralleli
abbiamo un array di record, detto anche
tabella, e per accedere all’i-esimo impiegato
utilizzeremo la componente i-esima dell’array:
ditta[i]
Trasformare array paralleli in
array di oggetti
• Per accedere ai campi si può scrivere
ditta[i].nome
ditta[i].stipendio
ditta[i].eta
• Secondo le regole della programmazione ad oggetti
noi, invece, definiremo private i campi della classe
Impiegato e costruiremo dei metodi di accesso:
ditta[i].nome()
ditta[i].stipendio()
ditta[i].eta()
Trasformare array paralleli in
array di oggetti
Trasformare array paralleli in
array di oggetti
• Esercizio. Costruire e stampare una tabella
archivio per contenere le informazioni sugli
studenti iscritti al Corso di Informatica 2-3.
• Soluzione. Costruiamo una classe Stud che
contiene informazioni minime su uno studente:
/**Classe minima per uno studente:
contiene il nome e cognome e la matricola
dello studente e i metodi per l'accesso ai
campi */
public class Stud {
private String nome;
private String cognome;
private int matricola;
• nome
• cognome
• matricola
• La classe conterrà il costruttore e i metodi di
accesso; poi costruiremo una classe di prova.
public Stud (String n, String c, int m){
nome = n;
cognome = c;
matricola = m;
}//fine costruttore
21
Trasformare array paralleli in
array di oggetti
/**restituisce il numero di matricola*/
public int matricola () {
return matricola;
}
/**restituisce il nome */
public String nome () {
return nome;
}
/**restituisce il cognome */
public String cognome () {
return cognome;
}
}//fine Stud
Trasformare array paralleli in
array di oggetti
import java.util.Scanner;
public class ProvaStud{
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Stud corso23[]= new Stud [120];
int i = 0;
String nome, cognome;
int matricola;
while(in.hasNext()) {
nome = in.next();
cognome = in.next();
matricola = in.nextInt();
Trasformare array paralleli in
array di oggetti
Trasformare array paralleli in
array di oggetti
if(matricola%10==2 || matricola%10==3){
corso23[i]=
new Stud(nome,cognome, matricola);
i++;
}
else System.out.println("\nlo studente “
+ cognome + " " + nome +
" appartiene ad altro corso");
System.out.println("\nStampa archivio");
for (int k=0 ;k<i;k++){
System.out.print(corso23[k].nome()+" ");
System.out.print(corso23[k].cognome());
System.out.println
(" " + corso23[k].matricola());
}//fine for
}//fine main
}
}
• Attenzione: i dati nel file devono essere coerenti con
l’acquisizione dei dati: scambiare il nome con il
cognome è un errore logico.
22