CORSO DI ALGORITMI E STRUTTURE DATI Homeworks set #1 Valerio Mennillo Alessia Trapani M63001277 M63001278 Esercizio 1.1 Notazione asintotica ļ· š(š) = š¶(š(š)š )? 1 1 Cerchiamo un controesempio. Supponiamo š (š) = š e š (š)2 = š2. š(š) Se calcoliamo š(š)2 = š, notiamo subito che š è una funzione illimitata e quindi in questo caso š(š) ≠ š(š(š)2 ) (possiamo arrivare allo stesso risultato applicando la definizione: non riusciremo a trovare una costante š affinché la disuguaglianza sia valida per š sufficientemente grande). Considerando, però, š (š) = š e š (š)2 = š2 , riusciamo a trovare š tale che 0 ≤ š ≤ šš2 per š sufficientemente grande. Infatti, per š > 0 š = š(š2 ) ⇒ š (š) = š(š(š)2 ). Possiamo concludere dicendo che š (š) = š(š(š)2 ) è a volte vera, dipende da š(š). Nota: se avessimo considerato solo funzioni crescenti, la relazione sarebbe stata sempre vera. ļ· š(š) + š¶(š(š)) = š£(š(š))? Consideriamo š (š) + š(š) per š(š) = š(š(š)). Dato che abbiamo supposto š(š) = š(š(š)), allora esiste una costante positiva š tale che 0 ≤ š(š) ≤ šš(š) per š sufficientemente grande. Fatte queste assunzioni possiamo dire che š (š) ≤ š(š) + š(š) ≤ (š + 1)š(š) per š sufficientemente grande. Possiamo concludere dicendo che š(š) + š(š(š)) = š©(š(š)) è sempre vera. ļ· š(š) = š(š(š)) š š(š) = š(š(š))? Se š(š) = Ω(š(š)), allora esiste una costante positiva š1 e š1 tale che š(š) ≥ š1 š(š) per š > š1 . Se š(š) = š(š(š)), allora per ogni costante positiva š e šš š(š) < šš(š) per š > šš . Per far valere entrambe contemporaneamente dovremmo avere che per š > ššš„(š1 , šš ) š1 š(š) ≤ š(š) < šš(š) Se š = š1 (la relazione o-piccolo vale se è verificata per ogni š), š1 š(š) ≤ š (š) < š1 š(š) Ciò è impossibile, quindi š(š) = Ω(š(š)) š š(š) = š(š(š)) non è mai vera. Esercizio 1.2 Complessità Dimostriamo che per qualsiasi š e š, con š > 0, (š + š)š = š©(šš ). Per fare ciò troviamo un limite superiore e un limite inferiore. ļ· (š + š )š = š(šš ) Troviamo š1 > 0 tale che (š + š)š ≤ š1 (šš ) per š sufficientemente grande. (š + š)š ≤ (š + |š|)š ≤ (š + š)š = (2š)š (š + š)š = š(šš ) ššš š ≥ |š| ššš š1 = 2š š š ≥ |š | ļ· (š + š )š = šŗ(šš ) Troviamo š2 > 0 tale che (š + š)š ≥ š2 (šš ) per š sufficientemente grande. š š (š + š )š ≥ (š − |š|)š ≥ ( ) ššš š ≥ 2|š| 2 (š + š)š = šŗ(šš ) Dato che š(š) = š©(š(š)) ⇔ ššš š2 = 2−š š š ≥ 2|š| š (š) = š(š(š)) š š(š) = šŗ(š(š)) (š + š)š = š©(šš ) con š1 = 2š , š2 = 2−š e per š ≥ 2|š| Esercizio 1.3 Complessità ļ· šš+š = š¶(šš )? Affinché š (š) = š(š(š)), deve esistere una costante positiva š tale che 2š+1 ≤ š2š per š sufficientemente grande. Cerchiamo š. 2š+1 ≤ š2š 2 ā 2š ≤ š2š La costante esiste, š = 2, e quindi 2š+1 = š(2š ) è vero. ļ· ššš = š¶(šš )? Affinché š (š) = š(š(š)), deve esistere una costante positiva š tale che 22š ≤ š2š per š sufficientemente grande. Cerchiamo š. 22š ≤ š2š 2š ā 2š ≤ š2š Non esiste una costante š tale che š ≥ 2š e quindi 22š = š(2š ) non è vero. Esercizio 1.4 Ricorrenze ļ· š Fornire il limite inferiore e superiore per š»(š) = šš» ( š) + š šš š Applichiamo il terzo caso del metodo dell’esperto. š=2 š=3 š (š) = š lg š ššššš š = šššš3 2 Verifichiamo le condizioni del teorema: 1. š(š) = šŗ(ššššš š+š ) per š > 0 š šš š = š (š) = šŗ(šššš3 2+š ) per š ≈ 0.4 š 2. šš (š ) ≤ šš(š) per una data š < 1 ed š sufficientemente grande š š 2 2 ( 3 ) šš ( 3 ) ≤ 3 š šš š 2 per š = 3 < 1 Entrambe le condizioni sono valide, quindi š (š) = š©(š šš š) ļ· š Fornire il limite inferiore e superiore per š»(š) = šš» ( š) + ššš š Applichiamo il primo caso del metodo dell’esperto. š=3 š=5 š(š) = lg 2 š ššššš š = šššš5 3 Dobbiamo verificare che š (š) = š(ššššš š−š ) per š > 0 šš2 š = š (š) = š(šššš3 2−š ) per 0 < š < ššš5 3 = 0.682 La condizione è valida, quindi š(š) = š©(šššš5 3 ) Esercizio 1.5 Ricorrenze Dimostriamo tramite albero di ricorrenza che la soluzione della ricorrenza š š š»(š) = š» ( ) + +š» ( š) + šš š š è Ω(n log n). 1 2 L’albero che ne viene fuori non è bilanciato perché vi è un percorso più veloce (a sinistra, (3 < 3),) relativo ai sottoproblemi più piccoli. Sommando i termini di ogni livello, notiamo che il costo è šš per tutti i livelli completi. Quando abbiamo livelli incompleti, il costo sarà ≤ šš. Per definire il limite inferiore vado, dunque, a considerare il percorso più breve visto che terminerà prima, 1 š ovvero quello più a sinistra che ha una dimensione pari a ( ) š. La dimensione sarà pari a 1 quando 3 1 š ( ) š=1 3 ⇒ š = ššš3 š š(š) sarà sicuramente maggiore del costo fino all’albero log 3 š quindi ššš3 š š (š) ≥ ∑ šš = šš(ššš3 š + 1) ≥ šš ššš3 š = š=0 š š ššš š ššš 3 ⇒ š(š) = šŗ(š ššš š) Homework 1.1. Inversioni ļ· Elencate le cinque inversioni dell’array (2, 3, 8, 6 ,1) 1. (1,5); 2. (2,5); 3. (3,4); 4. (3,5); 5. (4,5). ļ· Quale array con elementi estratti dall’insieme {š, š, … š} ha più inversioni? Quante inversioni ha? L’array con più inversioni è quello che presenta gli elementi in ordine decrescente: [š š − 1 š − 2 … 2 1] Calcoliamo il numero di inversioni partendo dal primo elemento (š) e andando avanti: - š avrà š − 1 inversioni; - š − 1 avrà š − 2 (š − 1 − 1) inversioni; - … - 2 avrà 1 inversione; - 1 avrà 0 inversioni. Possiamo quindi scrivere una sommatoria per ottenere il numero massimo di inversioni: š š š ∑š − 1 = ∑š −∑1 = š=1 š=1 š=1 š(š + 1) š(š − 1) −š = 2 2 ļ· Qual è la relazione tra il tempo di esecuzione di Insertion Sort e il numero di inversioni nell’array di input? Il numero massimo di inversioni si verifica quando il vettore è in ordine decrescente che coincide anche con il caso peggiore di Insertion Sort. Il tempo di esecuzione di Insertion Sort è strettamente legato al numero di volte in cui viene eseguito il ciclo while e, se abbiamo un alto numero di inversioni, andremmo ad eseguire tale ciclo un elevato numero di volte. Di conseguenza, più inversioni ci sono in un array, più tempo impiegherà Insertion Sort ad ordinarlo. ļ· Create un algoritmo che determina il numero di inversioni in una permutazione di n elementi nel tempo šÆ(š š„šØš š) nel caso peggiore. int Merge-Sort-Inversion(A,p,r) invers āµ 0 if p<r q āµ ⌊(p+r)/2⌋ invers āµ invers + Merge-Sort(A,p,q) invers āµ invers + Merge-Sort(A,q+1,r) invers āµ invers + Merge (A,p,q,r) return invers int Inversions-Counter(A,p,q,r) n1 āµ q-p+1 n2 āµ r-q //Create L[1 … n1+1] and R[1 … n2+1] for i āµ 1 to n1 do L[i] āµ A[p+i-1] for j āµ 1 to n2 do R[j] āµ A[q+j] L[n1+1] āµ R[n2+1] āµ ∞ iāµjāµ1 invers āµ 0 for k āµ p to r do if L[i]≤R[j] then A[k] āµ L[i] i āµ i+1 else then A[k] āµ R[i] invers āµ invers +n1-i+1 j āµ j+1 return invers Partendo dal Merge-Sort, è stato costruito un algoritmo che identifica le inversioni in A. Il principio su cui si basa è che il numero di inversioni viene incrementato solo se un elemento del sottoalbero di destra viene scelto per essere depositato nell’array superiore. Visto che i sottoarray sono già ordinati, possiamo incrementare il numero di inversioni di un numero pari a tutti i restanti elementi del sottoarray di sinistra. Homework 1.2 Unimodal int Unimodal(A) first āµ 0 last āµ lenght[A]-1 while first <= last do midpoint āµ ⌊(first + last)/2⌋ if midpoint + 1 > last then return midpoint if midpoint - 1 > first then return midpoint if A[midpoint] > A[midpoint-1] and A[midpoint] > A[midpoint+1] then return midpoint if A[midpoint] > A[midpoint+1] then last āµ midpoint-1 else first āµ midpoint+1 Il ragionamento alla base di questo algoritmo è molto simile a quello utilizzato per la ricerca binaria. Si prende l’elemento posto al centro dell’array e si controlla la relazione di tale elemento rispetto al precedente e al successivo. Si possono manifestare 3 casi: ļ· ļ· ļ· L’elemento centrale è maggiore di entrambi: l’elemento centrale è il massimo. L’elemento centrale è maggiore del successivo ma non del precedente: la ricerca viene ripetuta sugli elementi precedenti (da 0 a midpoint-1). L’elemento centrale è minore del successivo: la ricerca viene ripetuta sugli elementi successivi (da midpoint+1 a n). Il tempo di esecuzione dipende da quante volte viene eseguita la condizione del while nel caso peggiore. Il corpo del while viene eseguito fintanto che lo spazio di ricerca è non vuoto. Inizialmente lo spazio di ricerca ha dimensione š. Ad ogni esecuzione del while esso viene al più dimezzato, quindi dopo š iterazioni avrò uno spazio di ricerca pari a š š 2š . Considerando il caso peggiore, la dimensione sarà pari a 1 quando 2š = 1, ovvero š = šš š. Nel caso peggiore, il corpo del while viene eseguito lg š volte. Per tale motivo, l’algoritmo Unimodal ha una complessità pari a š(šš š). Homework 1.3 Ricorrenze ļ· š Fornire il limite inferiore e superiore per š»(š) = š» ( š) + šš Applichiamo il terzo caso del metodo dell’esperto. š=1 š=2 š (š) = 2š ššššš š = šššš2 1 = š0 = 1 Verifichiamo le condizioni del teorema: 3. š(š) = šŗ(ššššš š+š ) per š > 0 2š = š(š) = šŗ(šššš2 1+š ) = šŗ (š) per š = 1 š 4. šš (š ) ≤ šš(š) per una data š < 1 ed š sufficientemente grande 2š/2 ≤ š2š 1 2š/2 ≤ 2 2š = 2š−1 1 per š = 2 < 1 e š ≥ 2 Entrambe le condizioni sono valide, quindi š (š) = Θ(2š )