Algoritmi e Principi dell`Informatica Seconda Prova in Itinere

Algoritmi e Principi dell'Informatica
Seconda Prova in Itinere - 8 Febbraio 2013
Nome……………………………..Cognome……………………………….Matr……….
Laureando
Il tempo a disposizione è di 2 ore
Esercizio 1 (punti 5/15-esimi)
Esercizio 1.a
Si delinei una macchina di Turing M a nastro singolo che riconosca il linguaggio L ⊆ {a,b}*
consistente di tutte e sole le stringe con un numero di occorrenze di 'a' uguale a quello di 'b'.
La descrizione non deve necessariamente scendere in tutti i dettagli ma deve essere
sufficientemente chiara e precisa da permettere di calcolare la complessità temporale
asintotica della macchina: si spieghi con precisione quale sia tale complessità.
E' possibile trovare un altro modello di calcolo a potenza risolutiva minore della macchina a
nastro singolo (ossia non in grado di riconoscere tutti i linguaggi riconoscibili dalla MT) che
riconosca L con complessità inferiore a quella di M? Si motivi brevemente la risposta.
Esercizio 1.b
Si risolva lo stesso esercizio del punto 1.a con riferimento al linguaggio L' ⊆ {a, b, c}* le cui
stringhe hanno ugual numero di 'a', 'b' e 'c'.
Esercizio 2 (punti 8/15-esimi)
Si consideri un array che sia il risultato della visita in post-ordine sinistro di un Binary Search
Tree (BST) i cui nodi contengano numeri interi ordinati in ordine crescente (figlio di sinistra
< del padre ≤ del figlio di destra; linearizzato in: figlio di sinistra, figlio di destra, padre).
Assumendo l'ipotesi che il BST di partenza avesse entrambi i figli per ogni nodo interno, si
delinei, mediante opportuno pseudocodice, un algoritmo che, partendo dall'array linearizzato
ricostruisca il BST originario. Indi se ne valuti la complessità asintotica.
NB: il punteggio attribuito alla soluzione terrà conto della complessità asintotica
dell'algoritmo.
L’algoritmo in C
l'array ottenuta da visita post-order ha root come ultimo elemento, e subito prima di esso ha
right child. Invece il left child occupa la prima parte dell'array e si puo' individuare perche'
contiene solo elementi che sono minori di root
____________________________
|
|
|R|
----------------------------------------left childright child
-root
Occorre trovare il punto in cui finisce left child e inizia right child. Entrambi i figli poi si
costruiscono ricorsivamente dai loro valori post-order.
BST-fromPostOrder(value[] nodes) {
if (nodes == null) return null;
return recursiveFromPostOrder(nodes, 0, nodes.length - 1);
}
recursiveFromPostOrder(value[] nodes, int leftIndex, int rightIndex) {
if (rightIndex<leftIndex) return null; /*Empty segment->empty tree*/
if (rightIndex == leftIndex) return new BST(nodes[leftIndex]);/*single node ->
single node tree*/
rootval = nodes[rightIndex];
// Construct the root, left and right subtrees
root = new BST(rootval);
// we find the last index in the range [leftIndex .. rightIndex-1]
// that holds a value smaller than rootval
int leftLast = findLastSmaller(nodes, leftIndex, rightIndex-1, rootval);
root.left = recursiveFromPostOrder(nodes, leftIndex, leftLast);
root.right = recursiveFromPostOrder(nodes, leftLast + 1, rightIndex-1);
return root;
}
// la ricerca del punto di taglio fatta con ricerca dicotomica
int findLastSmaller(value[] nodes, int first, int last, int cut) {
// If the segment is empty, or the first value is larger than cut, e no value //
smaller than cut
if (last < first || nodes[first] > cut) return first - 1;
int low = first, high = last, mid;
while (low < high && nodes[high] > cut) {
// check the middle of the segment
mid = low + (high-low+1)/2;
if (nodes[mid] > =cut) {
// The last index of a value < cut is in the first half
high = mid-1;
} else {
low = mid;
}
}
return high;
}
Esercizio 3
(punti 3/15-esimi)
Si vuole progettare una tabella di hashing in cui memorizzare numeri primi, usando gli stessi
come chiave. La tabella è lunga 2n elementi, si sceglie di utilizzare la funzione
h(x) = x mod 2n come funzione di hash. E' una buona scelta? Motivare brevemente la
risposta.
Esercizio 4
(punti 2/15-esimi)
Si stabilisca l’ordine di grandezza (classe di Θ-equivalenza) della soluzione della seguente
equazione alle ricorrenze:
T(n) = 7 T(n/5) + Θ (n2)
Tracce di soluzioni
Esercizio 1
1.a
Una M a nastro singolo non può far altro che scorrere l'intero nastro, o una sua
porzione ad esso proporzionale, nel caso pessimo, per ogni carattere letto alla
ricerca di un suo corrispondente; alternativamente aggiornando un contatore che
dovrebbe trovarsi in una diversa porzione di nastro rispetto a quella dove è
memorizzata la stringa di ingresso. Quindi la sua complessità temporale deve
essere almeno Θ(n2).
Un automa a pila deterministico invece può facilmente riconoscere L in Θ(n).
1.b
La precedente macchina M può facilmente essere modificata in modo da
riconoscere L' con la stessa complessità asintotica.
L' però non è riconoscibile da alcun automa a pila; è tuttavia riconoscibile da una
rete di Petri, sempre in tempo lineare, che però non ha la potenza
computazionale delle MT.
Esercizio 2
Un esempio di BST che soddisfi l'ipotesi dell'esercizio e della sua linearizzazione
in post-ordine sinistro è fornito in figura.
13
11
12
8
5
3
15
14
18
8
6
(a) esempio di BST
(b) array risultante dalla visita in post-odine sinistro del BST in figura (a):
3, 6, 5, 8, 8, 12, 11, 14, 18, 15, 13.
Si noti che (per un certo valore) non possono esistere più di due nodi contenenti
quel valore, altrimenti si dovrebbe violare l'ipotesi che ogni nodo interno ha
entrambi i figli.
Un modo semplice di riottenere il BST di partenza consiste nel percorrere l'array
da destra a sinistra ed eseguire una serie di inserimenti in ordine dei vari
elementi. In questo modo però la complessità risulta (O(n.log(n)) nel caso di BST
bilanciato e O(n2) nel caso pessimo.
Un algoritmo più efficiente invece può fare uso di una struttura ausiliaria a pila
e procedere nel modo seguente, da sinistra a destra, sfruttando la proprietà che
gli elementi dell'array sono in ordine crescente tranne quando si passa da figlio
(necessariamente di destra) a padre:
push a[0]; crea un nodo per a[0]
for i := 1, …,n
do
while a[i-1] < a[i] push a[i]; crea un nodo per a[i]
crea un nodo per a[i]; assegna al suo campo figlio di destra il puntatore al
nodo creato per l'elemento in cima alla pila e al figlio di sinistra il
puntatore al nodo creato per l'elemento sottostante; pop; pop; push a[i];
L'algoritmo visita ogni elemento una sola volta e quindi ha complessità O(n).
Esercizio 3
La funzione di hash proposta non è una buona funzione: i numeri primi, fatto salvo 2, sono
tutti dispari; quindi i posti di posizione pari verranno utilizzati solo in caso di conflitti e la
tabella risulterà fortemente sbilanciata e/o sottooccupata.
Esercizio 4
Applicando il teorema dell’esperto (Master theorem) abbiamo:
a = 7, b = 5, f(n) = Θ (n2) > Θ (nlogb(a) + ε)
2
Quindi la soluzione è Θ (n ).