Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Divide et impera - java, Appunti di Fondamenti di informatica

Appunti università della calabria - ingegneria informatica

Tipologia: Appunti

2021/2022

In vendita dal 12/10/2019

erty89qs
erty89qs 🇮🇹

4.5

(6)

42 documenti

1 / 12

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
1
Divide et impera
Divide et impera
La tecnica detta divide et impera è una
strategia generale per impostare algoritmi (par.
9.4).
Consideriamo un problema Pe sia nla
dimensione dei dati, la strategia consiste nel:
suddividere il problema in ksottoproblemi 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 d ominate per poter
governare con più facilità.
Divide et impera
Se i ksottoproblemi 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
kil numero dei sottoproblemi
hla dimensione limite
Si può scrivere uno schema generale per la
scomposizione.
Divide et impera
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
pf3
pf4
pf5
pf8
pf9
pfa

Anteprima parziale del testo

Scarica Divide et impera - java e più Appunti in PDF di Fondamenti di informatica solo su Docsity!

Divide et impera

Divide et impera

  • La tecnica detta “ divide et impera ” è una strategia generale per impostare algoritmi (par. 9.4).
  • 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à.

Divide et impera

  • 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

algoritmo DIVETIMP (S, n) se n < h allora risolvere direttamente il problema P altrimenti dividere S in k sottoinsiemi risolvere separatamente i k sottoproblemi P 1 , …, Pk: DIVETIMP(S 1 ,n 1 ), … , DIVETIMP(Sk,nk) riunire i risultati ottenuti //finese //fine algoritmo

Divide et impera: complessità

  • 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(n 1 ) + T(n 2 ) + … 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.

Ordinamenti quicksort

e mergesort

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. (par. 9.4)

Quicksort

  • 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: 2 ·[(n/2) (n/2 – 1) /2] = n^2 /4 – n/
  • Confrontiamo le formule: n^2 /4 – n/2 < n^2 /2 – n/2 = (n^2 – n)/2 = = n ·(n-1) /

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].
  • Ci sono varie scritture dell’algoritmo quicksort, alcune ottimizzano il numero di confronti, ma lasciano inalterata la complessità.

Quicksort

  • Per realizzare la partizione avremo bisogno di 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.

Quicksort

algoritmo partizione(n1, n2, v) conf ← v[n1] i ← n1+ k ← n mentre i ≠ k eseguire mentre v[i] ≤conf e i ≠ k eseguire i ← i+ //fine mentre mentre v[k] ≥conf e i ≠ k eseguire k ← k- //finementre scambiare v[i] con v[k]

Quicksort

//finementre: ciclo esterno //sistemare conf nella posizione k=i: è al suo posto se v[k] > conf allora k ←k- //finese scambiare v[n1] con v[k] //fine partizione

  • E necessario il confronto tra conf e v[k]?

Quicksort

  • Esempio.

10 5 11 1 13 2 20 conf i=2 i=3 k=6 k=

i=4 i= k= i = k = 5 termina anche il ciclo esterno v[k]>conf (10>13) quindi k- 1 5 2 10 13 11 20

Quicksort

  • Esempio. 20 3 1 10 9 7 11 conf 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

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) è la 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.

Quicksort

  • Caso peggiore : vettore ordinato , la partizione è sbilanciata:

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)/ O(n^2 /2)

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

Mergesort

  • 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.
  • Progetto dell’algoritmo di fusione (merge) algoritmo merge(v,p,medio,q) h ← p i ← p k ← medio+

Mergesort

mentre h ≤ medio e k ≤ q eseguire se v[h] ≤ v[k] allora s[i] ← v[h] h ← h+ altrimenti s[i] ← v[k] k ← k+ finese i ← i+ finementre //ricopiare la parte di array non esaminata

Mergesort

se h = medio+ 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 chiamate ricorsive.

Mergesort

  • Complessità. Contiamo i confronti tra gli elementi dell’array: 0 se n = 0, T(n) = D(n) + C(n) + T(n/2) + T(n/2) D(n) = 0 calcolo di medio C(n) è la complessità dell’algoritmo di fusione C(n) = O(n): vengono considerati tutti gli elementi delle due parti lunghe n/

Mergesort

  • 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:

T(n) = n + T(n/2) + T(n/2) = n + 2T(n/2) = = n + 2(n/2 + 2T(n/4)) = 2n + 2^2 T(n/2^2 ) = = ….. = k·n + 2k·T(n/2k)

se n = 2k^ allora k = log 2 n O(n ···· log 2 n)

Mergesort e Quicksort

  • Confrontiamo i due algoritmi.
  • L’algoritmo mergesort ha la complessità più bassa nel caso peggiore O(nlog 2 n); esegue però molte ricopiature per eseguire la fusione.
  • L’algoritmo quicksort ha caso peggiore O(n^2 /2), ma nel caso favorevole e medio è O(nlog 2 n).
  • Alcuni linguaggi (Java) implementano un algoritmo sort per il problema dell’ordinamento utilizzando: l’ordinamento per inserimento per n<7, e quicksort negli altri casi.

Mergesort

  • Albero delle chiamate ricorsive. v0 v1 v2 v3 v4 v5 v6 v 1 0 8 5 4 3 -1 9

1 0 8 5 4 3 -1 9

1 0 8 5 4 3 -1 9

1 0 8 5 4 3 -1 9

0 1 (^) 5 8

0 1 5 8

Altri algoritmi per Fibonacci

  • Esempio. Vogliamo calcolare 4^8.

48 = 4 · 4 · …. · 4 8 42 = 16 162 = 4^4 = 256 2562 = 4^8 quindi in 3= log 28 passi abbiamo eseguito il calcolo.

  • In generale:

Mn^ = ( M n/2)^2 con n pari

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 funzione 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> allora potenzamatrice(M, n/2) M ← M * M //finese se n è dispari allora M ← M * A //finese //finealgoritmo

  • La complessità di tempo è O(log 2 n)
  • La complessità di spazio è O(1).

Puntatori o

riferimenti

Puntatori o riferimenti

  • Un puntatore è una variabile che contiene

l’indirizzo di un’area di memoria.

  • Quando si definisce una variabile, questa viene

allocata in memoria e viene individuata dal suo nome. È possibile anche accedere alla variabile tramite il suo indirizzo.

  • Per definire in C++ una variabile puntatore si

utilizza il simbolo ***** e si indica il tipo di dato dell’area di memoria.

Puntatori o riferimenti

  • Sintassi. *nometipo nomepuntatore;
  • Esempio. int i; i=5; *int p;

5

i

p

Puntatori o riferimenti

  • Con la definizione

*int p; si dice che p è un puntatore ad un’area di memoria di tipo int. Pertanto p può contenere l’indirizzo di memoria di una variabile di tipo int.

  • Per ottenere l’indirizzo di memoria di una variabile già allocata, si utilizza l’operatore & operatore “indirizzo_di”

Puntatori o riferimenti

  • Per collegare il puntatore p all’area allocata per la variabile i eseguiamo l’assegnazione:

p=&i;

  • In tale modo si può accedere ad i anche tramite p : *p è il contenuto dell’area “vista” da p.

5

p (^) i