Corso di Algoritmi e Strutture Dati—Informatica per il Management

Nome e Cognome ______________________________________ Matricola __________________
Corso di Algoritmi e Strutture Dati—Informatica per il Management
Prova Scritta, 18/7/2016
•
•
•
•
•
•
•
Chi deve recuperare il progetto del modulo 1 ha 1 ora e 30 minuti per svolgere gli esercizi 1, 2, 3
Chi deve recuperare il progetto del modulo 2 ha 1 ora e 30 minuti per svolgere gli esercizi 4, 5, 6
Chi deve sostenere la prova completa ha 2 ore e 30 minuti per svolgere tutti gli esercizi proposti.
Durante la prova è consentito consultare libri e appunti.
Non è consentito l'uso di dispositivi elettronici (ad esempio, cellulari, tablet, calcolatrici elettroniche...), né
interagire in alcun modo con gli altri studenti pena l'esclusione dalla prova, che potrà avvenire anche dopo il
termine della stessa.
Le risposte devono essere scritte a penna su questi fogli, in modo leggibile e adeguatamente motivate. Le parti
scritte a matita verranno ignorate. Eventuali altri fogli possono essere utilizzati per la brutta copia ma non
devono essere consegnati e non verranno valutati.
I voti saranno pubblicati su AlmaEsami e ne verrà data comunicazione all'indirizzo mail di Ateneo
(@studio.unibo.it). Lo studente ha 7 giorni lavorativi di tempo per rifiutare il voto; tutti i voti non
esplicitamente rifiutati entro tale data sono considerati accettati, e verranno in seguito verbalizzati.
BARRARE LA CASELLA CORRISPONDENTE ALLA PARTE SVOLTA
□ Svolgo solo la parte relativa al modulo 1 (esercizi 1, 2, 3);
□ Svolgo solo la parte relativa al modulo 2 (esercizi 4, 5, 6);
□ Svolgo tutto il compito
NON SCRIVERE NELLA TABELLA SOTTOSTANTE
Es. 1
/4
Es. 2
/2
/2
Es. 3
/2
/4
Es. 4
/2
/4
Es. 5
/2
/4
Es. 6
/6
Nome e Cognome ______________________________________ Matricola __________________
Esercizio 1. Scrivere un algoritmo che, dato in input un array A[1..n] di interi arbitrari avente
lunghezza n ≥ 2, restituisce:
• 1 se A è ordinato in senso non decrescente, ossia se A[1] ≤ A[2] ≤ … ≤ A[n];
• -1 se A è ordinato in senso decrescente, ossia se A[1] > A[2] > … > A[n];
• 0 se non vale nessuno dei due casi precedenti;
[punti 4]
Determinare il costo asintotico dell'algoritmo proposto, motivando la risposta
[punti 2]
Soluzione. Si puo' fare tutto in una unica passata, confrontando i primi due elementi di A per
decidere quale tra i primi due casi si puo' applicare, e verificare poi il resto
integer CONTROLLAVETTORE(integer A[1..n])
if ( A[1] ≤ A[2] ) then
// siamo nel caso 1 oppure 0
for integer i ← 2 to n - 1 do
if ( A[i] > A[i+1] ) then
return 0;
endif
endfor
return 1;
else
// siamo nel caso -1 oppure 0
for integer i ← 2 to n - 1 do
if ( A[i] ≤ A[i+1] ) then
return 0;
endif
endfor
return -1;
endif
L'algoritmo di cui sopra ha costo O(n) nel caso peggiore, e O(1) nel caso migliore. Il caso migliore
si verifica, ad esempio, quando A = [1, 2, 1, …]. Si noti che è SBAGLIATO scrivere (come ha fatto
qualcuno) che “il caso migliore è O(1) e si verifica quando l'array ha due soli elementi”.
Nome e Cognome ______________________________________ Matricola __________________
Esercizio 2. Si consideri il seguente albero binario:
A
B
C
D
G
E
H
F
I
J
Scrivere nelle caselle sottostanti i nomi dei nodi come comparirebbero durante una visita in
profondità in ordine anticipato (pre-visita: visita radice; visita ricorsiva sottoalbero sinistro; visita
ricorsiva sottoalbero destro)
[punti 2]
Scrivere nelle caselle sottostanti i nomi dei nodi come comparirebbero durante una visita in
profondità in ordine posticipato (post-visita: visita ricorsiva sottoalbero sinistro; visita ricorsiva
sottoalbero destro; visita radice)
[punti 2]
Nome e Cognome ______________________________________ Matricola __________________
Esercizio 3. La mappa stradale di una città in cui tutte le strade sono a doppio senso di circolazione
è organizzata come un grafo non orientato G = (V, E), dove V = {1, … n} è un insieme di n incroci,
ed E  V  V è l'insieme dei tratti di strada (a doppio senso) che collegano due incroci. A causa di
lavori di ristrutturazione, alcuni degli incroci non sono agibili, quindi nessuna delle strade a loro
incidenti puo' essere attraversata. Ad esempio, nel caso sottostante gli incroci 2, 6, 8 non sono
agibili, per cui il cammino minimo (quello che attraversa il numero minimo di archi) per chi deve
spostarsi dall'incrocio 1 all'incrocio 9 è 1 → 3 → 5 → 4 → 7 → 9
2
4
1
7
6
3
5
9
8
Scrivere un algoritmo efficiente che, dato in input il grafo non orientato G = (V, E) e un array
booleano P[1..n] (dove n è il numero di nodi in G) tale che P[i] = true se e solo se il nodo i è
percorribile, determina la lunghezza del cammino minimo (cioé quello che attraversa il numero
minimo di archi, se esiste) che collega il nodo 1 con il nodo n. Se tale cammino non esiste
l'algoritmo restituisce +.
[punti 4]
Determinare il costo asintotico dell'algoritmo proposto, motivando la risposta.
[punti 2]
Soluzione. Si modifica l'algoritmo di visita in ampiezza, evitando gli archi che portano a nodi non
percorribili (cioè evitando i nodi w per i quali si abbia P[w] = false).
Nome e Cognome ______________________________________ Matricola __________________
Esercizio 4. A lezione è stato illustrato il seguente algoritmo che esegue le mosse necessarie per
risolvere il problema delle Torri di Hanoi con n dischi:
HANOI(Stack p1, Stack p2, Stack p3, integer n)
if (n = 1) then
p3.push(p1.pop())
else
HANOI(p1, p3, p2, n-1)
p3.push(p1.pop())
HANOI(p2, p1, p3, n-1)
endif
Scrivere un algoritmo (modificando quello sopra, o sviluppandone uno diverso) che restituisca il
numero di mosse necessarie per risolvere il problema delle Torri di Hanoi con n dischi.
[punti 4]
Dare una stima del costo asintotico dell'algoritmo proposto, motivando la risposta.
[punti 2]
Soluzione. La soluzione piu' semplice consiste nel modificare l'algoritmo di cui sopra (non serve
tenere traccia dei tre stack, basta solo il parametro n = numero dischi)
integer HANOI(integer n)
if ( n = 1 ) then
return 1;
else
return HANOI(n – 1) + 1 + HANOI(n – 1) ;
endif
Questa soluzione ha costo O(2n), come l'algoritmo che “esegue” le mosse (la dimostrazione è stata
vista a lezione; si noti che non si puo' applicare il Master Theorem perché l'equazione di ricorrenza
non è del tipo che puo' essere risolta con esso).
Possiamo migliorare l'algoritmo osservando che le due chiamate ricorsive Hanoi(n – 1)
restituiscono lo stesso valore, quindi possiamo effettuarne solo una:
integer HANOI(integer n)
if ( n = 1 ) then
return 1;
else
return 2 * HANOI(n – 1) + 1;
endif
ottenendo un algoritmo di costo O(n).
In realtà si puo' risolvere il problema analiticamente: il numero di spostamenti S(n) soddisfa
l'equazione di ricorrenza S(n) = 2S(n - 1) + 1, S(1) = 1, che ha come soluzione S(n) = 2n – 1. Il
calcolo di S(n) può essere effettuato in tempo O(log n) usando l'algoritmo “veloce” per l'elevamento
a potenza intera (https://en.wikipedia.org/wiki/Exponentiation_by_squaring); questa ulteriore
ottimizzazione comunque non era richiesta, e tutti coloro che hanno descritto l'implementazione in
tempo O(n) hanno ottenuto punteggio pieno.
Si noti che soluzioni del tipo:
integer HANOI(Stack p1, Stack p2, Stack p3, integer n)
integer k = 1;
Nome e Cognome ______________________________________ Matricola __________________
if (n = 1) then
p3.push(p1.pop());
return k;
else
HANOI(p1, p3, p2, n-1)
p3.push(p1.pop())
k ← k + 1;
HANOI(p2, p1, p3, n-1)
endif
return k;
e simili sono errate, in quanto la variabile k è locale a ciascuna invocazione della procedura Hanoi(),
quindi il suo valore non viene “ereditato” in alcun modo dal chiamante. Il fatto che la funzione
sopra sia errata è evidente considerando che Hanoi() restituisce un valore, ma tale valore è ignorato
nell'invocazione delle chiamate ricorsive (nel ramo “else” dell'”if”). Come regola generale, ogni
funzione ricorsiva il cui valore venga ignorato dalle chiamate ricorsive denota quasi sicuramente un
errore.
Nome e Cognome ______________________________________ Matricola __________________
Esercizio 5. Scrivere la tabella di istruzioni di una Macchina di Turing che risolve il problema
seguente. La macchina opera su un alfabeto che contiene i simboli {blank, 0, 1}. Inizialmente il
nastro contiene un numero binario composto dai simboli 0 e 1 scritti su celle adiacenti, e la testina
di lettura-scrittura è posizionata sulla prima cifra a sinistra. La MdT deve scorrere il numero binario
da sinistra a destra, e rimpiazzare la prima occorrenza della cifra 1 con 0.
A titolo di esempio, la tabella seguente mostra quale deve essere il contenuto finale del nastro in
alcuni esempi.
Contenuto iniziale del nastro
Contenuto finale del nastro
000
000
000101
000001
110010
010010
00001
00000
[punti 4]
Soluzione. Basta un unico stato q0:
Stato corrente
Simbolo corrente
Nuovo simbolo
Nuovo stato
Spostamento
q0
0
0
q0
right
q0
1
0
halt
right
q0
blank
blank
halt
right
Nome e Cognome ______________________________________ Matricola __________________
Esercizio 6. Una emittente televisiva deve organizzare il palinsesto di una singola giornata di
trasmissioni, composta da 24 ore (1440 minuti). L'emittente deve comporre il palinsesto scegliendo
un opportuno sottoinsieme di n programmi a disposizione, aventi durate T[1], ... T[n] minuti
rispettivamente. Tutte le durate sono numeri interi. Ciascun programma puo' essere mandato in onda
al massimo una volta; inoltre, dopo ogni programma è necessario inserire un intervallo di 5 min da
destinare agli spot pubblicitari. Tale intervallo deve essere inserito anche dopo l'ultimo programma
della giornata.
Scrivere un algoritmo efficiente, basato sulla programmazione dinamica, che dati in input l'array
delle durate T[1..n], restituisce true se e solo se esiste un opportuno sottoinsieme di programmi la
cui durata complessiva, sommata alla durata degli intervalli pubblicitari in coda a ciascuno, sia
esattamente uguale a 1440 minuti (cioè 24 ore). Ad esempio, considerando un caso semplice con
n = 10 programmi aventi durate T = [500, 140, 180, 80, 310, 300, 200, 295, 60, 30] l'algoritmo deve
restituire true in quanto scegliendo i programmi numero 1, 2, 3, 6, 8, si ottiene una durata totale pari
a (500 + 5) + (140 + 5) + (180 + 5) + (300 + 5) + (295 + 5) = 1440 minuti.
[punti 6]
Soluzione: E' il problema dello zaino, con l'unica variante che la durata effettiva di ciascun brano,
da considerare ai fini della programmazione dinamica, non è T[i] ma T[i] + 5, in quanto bisogna
sempre tenere conto dei 5 minuti in coda di pubblicità.