Esame di Algoritmi e Strutture Dati
Corso di Laurea in Ingegneria Informatica
Canali A-L, M-Z
Anno Accademico 2002-2003
9 luglio 2002-03
Domanda 1, punti 6 Si consideri la seguente classe Java, in cui, per semplicità, sono
indicate solo le segnature dei metodi pubblici.
public class BSTree {
public BSTree(); // costruttore: crea BST vuoto
public BSTNode insert (int key); // return rif. a nodo inserito o null
public BSTNode delete (int key); // return rif. a nodo elimin. o null
public BSTNode search (int key); // return rif. a nodo trovato o null
public int[] preOrder(); // return array con chiavi visitate
public int[] inOrder(); // return array con chiavi visitate
public int[] postOrder(); // return array con chiavi visitate
public int[] bfs(); // return array con chiavi visitate
}
Scrivere una metodo Java, con segnatura public static void sort(int[] data),
che ordini un array di int sfruttando i servizi della classe BSTree. Si discuta il costo
computazionale dell’algoritmo.
Possibile soluzione
Assumiamo che nell’array data non ci siano elementi ripetuti.
Si inseriscono gli elementi in un BST e si esegue una visita simmetrica, che
restituisce l’array degli elementi del BST ordinati:
public static void sort(int[] data) {
BSTree aTree = new BSTree();
for(int i = 0; i < data.length; i++)
aTree.insert(data[i]);
int[] temp = aTree.inOrder();
for(int i = 0; i < data.length; i++)
data[i] = temp[i];
}
Sia CI(n) il costo dell’inserimento dell’n-esimo elemento. Si ha:
costo
= CI(0) + CI(1) + … + CI(n-1)
+ O(n)
+ O(n)
=
(inserimenti)
(inOrder())
(copia array)
Σ C (i) + O(n)
I
Se CI(n)=O(log n) (caso medio), allora costo=O(n log n);
se CI(n)=O(n) (caso peggiore),
allora costo=O(n2)
1/5
Domanda 2, punti 6 Si consideri la seguente equazione di ricorrenza.
n =1
a
F (n ) = 
F (n − 1) + cn n > 1
a. Individuare un algoritmo di ordinamento la cui funzione di costo temporale è
esprimibile tramite la F(n) definita.
b. Determinare una delimitazione superiore per la funzione F(n)
Possibile soluzione
Un algoritmo di ordinamento la cui funzione di costo è esprimibile tramite la
F(n) è SelectionSort.
Infatti SelectionSort di volta in volta cerca il massimo in un array di n, n-1, n2, …, 1 elementi.
In altri termini una descrizione ricorsiva di SelectionSort è la seguente (per n
> 1):
cerca il massimo tra gli n elementi e ponilo in prima posizione (costo: cn)
esegui ricorsivamente SelectionSort sul rimanente sottoarray di n-1
elementi (costo: F(n-1))
da cui si vede che il costo è proprio F(n) = F(n-1) + cn.
Nel caso base naturalmente il costo è costante e pari a F(1) = a.
Una delimitazione superiore al valore di F(n) è dunque O(n2).
2/5
Domanda 3, punti 6 Fornire un'istanza di albero AVL in cui esiste un nodo la cui
cancellazione causa un numero di operazioni di ribilanciamento superiore a uno. Illustrare
l'evoluzione dell'albero in seguito a tale cancellazione.
Possibile soluzione
Cancello C
D è sbilanciato
E è sbilanciato
3/5
Rotazione
Rotazione
Domanda 4, punti 6 Scrivere un metodo Java, con segnatura public static
AVLNode search_i(AVLNode root, int i), che restituisce l’i-esimo elemento
memorizzato in un albero AVL con costo computazionale proporzionale all’altezza
dell’albero. Si utilizzi la seguente definizione:
class AVLNode {
int element;
AvlNode left;
AvlNode right;
int size;
}
dove size indica il numero di elementi memorizzati nel sottoalbero avente come radice il
nodo.
Possibile soluzione
L’idea è quella di visitare l’albero AVL scendendo direttamente verso il nodo iesimo. Di volta in volta ci si chiede se bisogna scendere nel sottoalbero sinistro
o nel sottoalbero destro. In particolare, se stiamo cercando il nodo j-esimo di
un sottoalbero avente il nodo v come radice:
se il sottoalbero sinistro di v ha almeno j nodi, allora il nodo j-esimo si
trova nel sottoalbero sinistro;
se il sottoalbero sinistro di v ha j-1 nodi, il nodo j-esimo è proprio v;
altrimenti il nodo j-esimo (se esiste) si trova nel sottoalbero destro di v.
L’algoritmo è il seguente:
public static AVLNode search_i(AVLNode root, int i) {
if(root == null) return null;
if(root.left == null)
if(i == 1) return root;
else return search_i(root.right, i - 1);
else if(root.left.size >= i)
return search_i(root.left, i);
else if(i == root.left.size + 1)
return root;
else return search_i(root.right, i – 1 - root.left.size);
}
Poiché in ogni chiamata ricorsiva si effettua un numero costante di operazioni
e si scende di un livello, il costo è proporzionale all’altezza dell’albero.
4/5
Domanda 5, punti 6 Descrivere un algoritmo che, dato un grafo diretto G, verifichi se G
è aciclico. Si discuta il costo computazionale dell’algoritmo.
Possibile soluzione
Si può impiegare l’algoritmo di ordinamento topologico: l’algoritmo ha
successo se e soltanto se G è un grafo aciclico.
boolean acyclicityTest(Graph G = (V, E)) {
i = j = 1;
for(v ε V) {
num(v) = 0;
fin(v) = 0;
}
while(∃v ε V con num(v) == 0)
if(!TS(v)) return false;
return true;
}
boolean TS(Node v) {
num(v) = i++;
for(ogni nodo w adiacente a v)
if(num(w) == 0) {
if(!TS(w)) return false;
} else if(fin(w) == 0)
return false; //scoperto un ciclo
fin(v) = j++;
return true;
}
Il costo è quello della DFS, ossia O(m + n) con m=|E|, n=|V|.
5/5