• Non ci sono risultati.

Scrivere l’algoritmo della funzione bilancia() (in pseudocodice o in python) che accetti in input tale albero ​A e restituisca un nuovo albero binario di ricerca ​B che risulti bilanciato

N/A
N/A
Protected

Academic year: 2021

Condividi "Scrivere l’algoritmo della funzione bilancia() (in pseudocodice o in python) che accetti in input tale albero ​A e restituisca un nuovo albero binario di ricerca ​B che risulti bilanciato"

Copied!
5
0
0

Testo completo

(1)

Ingegneria degli Algoritmi  21 febbraio 2020   

 

Esercizio 1 

Trovare i limiti superiore e inferiori più stretti possibili per la seguente equazione di ricorrenza:  

   

 

Soluzione 

Poiché l’equazione di ricorrenza ha una componente non ricorsiva, è immediato affermare che sia        .

(n ) Ω 2  

Per completare lo studio, procediamo per tentativi. Appurato che sia        (n )Ω 2 , è lecito chiederci se sia        . Per dimostrarlo, rimane da provare che sia anche .

(n )

Θ 2 O(n )2  

 

● Caso base: T(n)= 1 ≤ cn2, per tutti i valori di compresi tra 1 e 6, ovvero:n  

  Tutte queste disequazioni sono soddisfatte da c ≥ 1

● Ipotesi induttiva: T(n)≤ ck2, per k < n

● Passo induttivo: 

  L’ultima disequazione è soddisfatta da c ≥ 12. 

 

Abbiamo quindi dimostrato che T(n)= (n )Θ 2 per n0= 1 e c ≥ 12.   

Esercizio 2 

Sia dato un albero binario di ricerca ​A non bilanciato. Scrivere l’algoritmo della funzione        bilancia() (in pseudocodice o in python) che accetti in input tale albero       ​A e restituisca un nuovo      albero binario di ricerca ​B che risulti bilanciato. Fornire un istanza di input e mostrare i passi che        vengono compiuti da tale algoritmo sull’istanza di esempio. Discutere informalmente la complessità        computazionale dell’algoritmo proposto. 

 

Soluzione 

Un approccio “semplice” per risolvere questo esercizio consiste nell’eseguire una visita in ordine        simmetrico dell’albero binario in input ed inserire i nodi, uno ad uno, all’interno di un albero        auto-bilanciato (come un RB-tree o un AVL). Questa soluzione ha una complessità pari ad          (n logn)O ,  dovuta alla scansione di tutti i nodi e dal costo necessario all’inserimento (e ribilanciamento)        dell’albero di destinazione. 

(2)

Una soluzione più elegante può essere implementata utilizzando un vettore di appoggio, costruito        effettuando comunque una visita in ordine simmetrico. Si noti che, poiché l’albero in input seppur        sbilanciato è comunque un albero binario di ricerca, la visita in ordine simmetrico produce un vettore        in cui gli elementi sono ordinati. Questa visita ha costo O(n)

 

Dato un vettore ordinato, sfruttando la proprietà degli alberi binari di ricerca, è possibile costruire un        albero di ricerca bilanciato sfruttando la tecnica del ​divide et impera          ​. Infatti, scegliendo      ricorsivamente come radice di un sottoalbero l’elemento “al centro” del (sotto)vettore, si garantisce        che i sottoalberi destro e sinistro siano composti dallo stesso numero di elementi. Anche questa        costruzione, che visita gli elementi del vettore tutti una e una sola volta, ha costo      O(n). La    complessità computazionale dell’algoritmo finale, quindi, avrà costo O(n)

 

Una possibile implementazione in python è la seguente. Lo pseudocodice ricalca questa        implementazione in maniera molto simile.. 

 

1. class​ Node:

2. ​def​ __init__(self,data):

3. self.data=data 4. self.left=None 5. self.right=None 6.

7. def​ BST2Array(root, arr):

8. ​if​ ​not​ root:

9. ​return

10. BST2Array(root.left, arr) 11. arr.append(root)

12. BST2Array(root.right, arr) 13.

14. def​ Array2BST(arr, start, end):

15. ​if​ start > end:

16. ​return​ None

17. mid = (start + end) // 2 18. node = arr[mid]

19. node.left = Array2BST(arr, start, mid - 1) 20. node.right = Array2BST(arr, mid + 1, end) 21. ​return​ node

22.

23. def​ bilancia(A):

24. arr = []

25. BST2Array(A, arr)

26. ​return​ Array2BST(arr, 0, len(arr) - 1)  

   

(3)

Un input di esempio, ed i passi di esecuzione dell’algoritmo, sono i seguenti. 

   

Vettore di appoggio dopo la visita in ordine simmetrico: 

 

1 2 3 4 5 6 7

 

La costruzione dell’albero bilanciato avviene prendendo come radice l’elemento centrale,        assegnando la porzione destra e sinistra del vettore ai sottoalberi destro e sinistro rispettivamente, e        ripetendo ricorsivamente la costruzione, come segue. 

 

   

     

(4)

Esercizio 3 

Nel gioco di Minecraft, una carta geografica altimetrica è rappresentata da una matrice        n · n    di celle  quadrate, ognuna delle quali riporta un’altitudine intera misurata in blocchi. Un valore minore o        uguale a zero indica un mare, mentre un valore positivo indica un’isola. 

Scrivere un algoritmo che prenda in input una matrice A di interi e la sua dimensione positiva , e        n     restituisca l’altezza media dell’isola più elevata. 

Discutere informalmente la correttezza della soluzione proposta e calcolare la complessità        computazionale. 

Ad esempio, nella matrice seguente ci sono tre isole, una      2 · 3    in alto a sinistra con altezza media        , una a destra con altezza media , una in basso a sinistra con altezza media . L’isola .5

1     4 · 1       1     1 · 2       2    

con altezza media più alta è quella in basso a sinistra, e quindi l’algoritmo deve restituire .2    

1  1  1  0  1 

2  2  2  -1  1  0  -1  0  -2  1  0  0  -1  -2  1  2  2  -1  0  0   

Soluzione 

Il problema può essere risolto facilmente interpretando la matrice come se fosse un grafo. Le celle        con valori minori o uguali a zero non ci interessano e possono essere “scartate” nella trasformazione        da matrice a grafo. Ogni cella della matrice è quindi un nodo, connesso alle sole celle adiacenti che        hanno un valore maggiore di zero. La matrice di esempio può quindi essere trasformata nel        seguente grafo. 

 

   

A questo punto, è sufficiente effettuare una visita in ampiezza di ciascuna componente connessa del        grafo, calcolando la somma delle chiavi dei nodi e contando il numero di nodi visitati. La scelta        dell’altezza media massima diviene a questo punto banale. 

 

Nell’implementazione dell’algoritmo, occorre necessariamente sfruttare una struttura dati        d’appoggio (ad esempio, matrice/vettore di booleani, coda, insieme, ...), per garantire che una        singola componente connessa sia visitata una ed una sola volta dall’algoritmo. Si noti che, nello        pseudocodice proposto più avanti, si utilizza una funzione di appoggio che restituisce due valori di       

(5)

 

isolaPiuAlta(M, n):

maxH ← 0

visited ← [0] * (n*n) for​ r ← 1 ​to​ n:

for​ c ← 1 ​to​ n:

if​ M[r][c] > 0 ​and not​ visited[r*c] ​then​:

height, count ← isolaDFS(M, n, r, c, visited) maxH = max(maxH, height / count)

return​ maxH

isolaDFS(M, n, r, c, visited)

if​ 1≤r<n ​and​ 1≤c<n ​and​ M[r][c] > 0 ​and not​ visited[r*c] ​then​:

visited[r*c] ← 1

h1, c1 ← isolaDFS(M, n, r − 1, c) h2, c2 ← isolaDFS(M, n, r + 1, c) h3, c3 ← isolaDFS(M, n, r, c − 1) h4, c4 ← isolaDFS(M, n, r, c + 1) height ← h1 + h2 + h3 + h4 + M[r][c]

count ← c1 + c2 + c3 + c4 + 1 return height, count

return 0, 0  

L’algoritmo di ​isolaPiuAlta() itera su tutta la matrice, pertanto è garantito che se un’isola esiste,        questa verrà individuata. L’utilizzo di un vettore ​visited condiviso tra le due funzioni garantisce che        ogni cella di un’isola sia visitata una ed una sola volta. 

L’algoritmo di ​isolaDFS() esplora in tutte le direzioni, ricorsivamente, a partire da una cella di        un’isola. È pertanto garantito che tutta l’isola verrà esplorata. Il numero di invocazioni ricorsive        potrebbe essere ridotto—vengono effettuate molte chiamate inutili—tuttavia la soluzione presentata        è corretta. 

Poiché ogni cella può essere visitata una ed una sola volta, la complessità è data dall’istruzione        dominante di ​isolaPiuAlta()​, ovvero il controllo nell’if. Pertanto, la complessità di questo        algoritmo è O(n )2

 

Sarebbe analogo trasformare esplicitamente la matrice in un grafo, inserendo all’interno di ciascun        nodo un flag per indicare se tale nodo è già stato visitato o meno. A quel punto, si sarebbe potuto        utilizzare un algoritmo di DFSpiù “tradizionale”. La complessità computazionale asintotica non        cambierebbe, ma chiaramente avremmo costanti nascoste più elevate, poiché pagheremmo        comunque il costo di trasformazione da matrice a grafo. 

Riferimenti

Documenti correlati

una quercia. A  su per il tronco di  si stava arrampicando  tutto sudato e sporco da  scuola, 

Un albero binario si dice degenere se ha n nodi e altezza n −1, cioè se ogni nodo interno ha un solo figlio. Osservazioni: Per

Un arco che attraversa un taglio si dice leggero se ha peso pari al minimo tra i pesi di tutti gli archi che attraversano tale taglio..

Dato un albero binario, progettare un algoritmo efficiente che stampi le chiavi di tutti e soli i nodi 0-bilanciati e analizzarne la complessit` a..

costruire un albero binario di ricerca Marco Lapegna – Laboratorio di Programmazione 2 Cenni agli alberi. procedura ricerca(in: radice, e;

[r]

[r]

Sembravano tutti aspettare qualcosa e visto che tutti erano tranquilli Il cerbiatto pensò che fosse normale per gli esseri umani avere gli alberi dentro casa!. Così pensando