Esercizi di Algoritmi e Strutture Dati
Moreno Marzolla
[email protected]
Ultimo aggiornamento: 10 novembre 2010
1
La “bandiera nazionale”
(problema 4.7 del libro di testo). Il problema della bandiera nazionale italiana
è cosı́ definito. Sia A[1..n] un array i cui elementi possono assumere solo uno di
tre possibili valori: bianco, verde e rosso. Vogliamo ordinare l’array in modo che
tutti gli elementi verdi precedano quelli bianchi, e gli elementi bianchi precedano
quelli rossi. L’algoritmo deve richiedere tempo O(n) nel caso peggiore, può
solo scambiare elementi e non deve usare altri array temporanei di appoggio,
quindi al più deve richiedere memoria aggiuntiva O(1). Non può nemmeno
utilizzare contatori per tenere traccia del numero di elementi di un certo colore
presenti (quindi non si può usare una variante dell’algoritmo counting sort,
che vedremo a lezione). L’algoritmo, infine, deve ordinare gli elementi facendo
una singola scansione dell’array. Assicurarsi che l’algoritmo funzioni anche se
mancano elementi di qualcuno dei colori.
Suggerimento: ripensare a come funziona la procedura partition dell’algoritmo
Quick Sort, e provare ad adattarla per risolvere questo problema.
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
B a n d i e r a . java - questo p r o g r a m m a risolve il p r o b l e m a 4.7 p . 116 di
Demetrescu , Finocchi , Italiano , " A l g o r i t m i e s t r u t t u r e dati "
( seconda e d i z i o n e ) , McGraw - Hill , 2008.
Il p r o b l e m a della b a n d i e r a n a z i o n a l e e ’ d e f i n i t o nel modo s e g u e n t e :
Sia A un array di d i m e n s i o n e n , i cui e l e m e n t i possono a s s u m e r e
solo uno di tre p o s s i b i l i colori : bianco , verde e rosso . V o g l i a m o
o r d i n a r e l ’ array in modo che tutti gli e l e m e n t i verdi p r e c e d a n o
quelli bianchi , e gli e l e m e n t i bianchi p r e c e d a n o quelli
rossi . L ’ a l g o r i t m o deve r i c h i e d e r e tempo O ( n ) nel caso peggiore ,
puo ’ solo s c a m b i a r e el e m e n t i e non deve usare altri array
t e m p o r a n e i di a p p o g g i o . Non puo ’ nemmeno u t i l i z z a r e c o n t a t o r i per
tenere traccia del numero di e l e m e n t i di un certo colore p r e s e n t i
( quindi non si puo ’ usare una v a r i a n t e dell ’ a l g o r i t m o c o u n t i n g
sort , che vedremo a lezione ). L ’ algoritmo , infine , deve o r d i n a r e
gli e l e m e n t i facendo una singola s c a n s i o n e dell ’ array . A s s i c u r a r s i
che l ’ a l g o r i t m o f u n z io n i anche se mancano e l e m e n t i di q u a l c u n o dei
colori .
1
* Version 0.3 del 2 0 0 9 / 1 1 / 2 5
* Autore : Moreno M a r z o l l a ( m a r z o l l a ( at ) cs . unibo . it )
*
* This file has been r e l e a s e d by the author in the Public Domain
*/
public class Bandiera {
public static final int VERDE = 1;
public static final int BIANCO = 2;
public static final int ROSSO = 3;
/* *
* Scambia tra di loro gli e l e m e n t i A [ i ] e A [ j ]. A t t e n z i o n e :
* nessun c o n t r o l l o viene e f f e t t u a t o circa la v a l i d i t a ’ degli
* indici i e j .
*/
protected static void swap ( int [] A , int i , int j )
{
System . out . println ( " Scambia " + i + " con " + j );
int tmp = A [ i ];
A [ i ] = A [ j ];
A [ j ] = tmp ;
}
/* *
* Stampa a video il c o n t e n u t o dell ’ array A
*/
public static void stampa ( int [] A )
{
for ( int i =0; i < A . length ; ++ i ) {
System . out . print ( A [ i ]);
System . out . print ( " " );
}
System . out . println ();
}
/* *
* Risolve il p r o b l e m a della b a n d i e r a n a z i o n a l e : ordina l ’ array
* A [] ( che puo ’ c o n t e n e r e solo i valori 1= VERDE , 2= BIANCO ,
* 3= ROSSO ) in una unica passata .
*
* Questo metodo ha c o m p l e s s i t a ’ Theta ( n ).
*/
public static void ordina ( int [] A )
{
int n = A . length ; // numero e l e m e n t i de
int v = 0; // Prima p o s i z i o n e in cui si puo ’ i n s e r i r e un e l e m e n t o verde
int i = 0; // Prima p o s i z i o n e in cui si puo ’ i n s e r i r e un e l e m e n t o bianco
int r = A . length -1; // Prima p o s i z i o n e in cui si puo ’ i n s e r i r e un e l e m e n t o rosso
/* *
* L ’ a l g o r i t m o m a n t i e n e le s e g u e n t i i n v a r i a n t i :
*
* 1. Gli e l e m e n t i A [0]... A [v -1] sono verdi ( se v =0 , non ci
* sono e l e m e n t i verdi );
*
* 2. Gli e l e m e n t i A [ v ].. A [i -1] sono bianchi ( se i == v , non ci
2
* sono e l e m e n t i bianchi );
*
* 3. Gli e l e m e n t i A [ r +1].. A [n -1] sono rossi ( se r == n -1 , non
* ci sono e l e m e n t i rossi );
*
* 4. Gli e l e m e n t i A [ i ]... A [ r ] possono essere di q u a l s i a s i colore
*
* Ad ogni iterazione , l ’ a l g o r i t m o esamina A [ i ] , e f f e t t u a n d o
* o p p o r t u n i scambi per s p o s t a r e l ’ e l e m e n t o nella p o s i z i o n e
* c o r r e t t a . Ad ogni iterazione , i viene i n c r e m e n t a t o OPPURE r
* viene d e c r e m e n t a t o . Cio ’ a s s i c u r a che sia g a r a n t i t a la
* t e r m i n a z i o n e dell ’ a l g o r i t m o .
*/
while ( i <= r ) {
if ( BIANCO == A [ i ] ) {
i ++;
} else {
if ( VERDE == A [ i ] ) {
swap ( A , v , i );
v ++;
i ++;
} else { // A [ i ] e ’ rosso
swap ( A , i , r );
r - -;
}
}
}
}
public static void main (
{
int p1 [] = {1 , 2 , 3 ,
Bandiera . ordina ( p1 );
Bandiera . stampa ( p1 );
int p2 [] = {1 , 1 , 1 ,
Bandiera . ordina ( p2 );
Bandiera . stampa ( p2 );
int p3 [] = {2 , 2 , 2 ,
Bandiera . ordina ( p3 );
Bandiera . stampa ( p3 );
int p4 [] = {3 , 3 , 3 ,
Bandiera . ordina ( p4 );
Bandiera . stampa ( p4 );
int p5 [] = {3 , 1 , 1 ,
Bandiera . ordina ( p5 );
Bandiera . stampa ( p5 );
int p6 [] = {3 , 3 , 3 ,
Bandiera . ordina ( p6 );
Bandiera . stampa ( p6 );
}
String [] args )
1 , 3 , 1 , 2 , 3 , 3 , 1 , 1 , 2 , 3 , 1 , 1 };
1};
2};
3};
3 , 1 , 3};
2};
}
3
2
Un problema apparentemente complicato
(problema 4.8 del libro di testo). Descrivere un algoritmo che dato un array A[1..n] di interi appartenenti all’insieme {1, 2, . . . k}, preprocessa l’array
in tempo O(n + k) in modo da generare una opportuna struttura dati che consenta di rispondere in tempo O(1) a query del tipo: “quanti elementi di A sono
compresi nell’intervallo [a, b]?” (per qualsiasi 1 ≤ a ≤ b ≤ k)
/*
* I n t e r v a l l i . java - questo p r o g r a m m a risolve il p r o b l e m a 4.9 p . 116
* di Demetrescu , Finocchi , Italiano , " A l g o r i t m i e s t r u t t u r e dati "
* ( seconda e d i z i o n e ) , McGraw - Hill , 2008.
*
* D e s c r i v e r e un a l g o r i t m o che dato un array A di n numeri interi ,
* tutti c o m p r e s i nell ’ i n t e r v a l l o [1 , k ] , p r e p r o c e s s a l ’ array in tempo
* O ( n + k ) in modo da g e ne r a r e una o p p o r t u n a s t r u t t u r a dati che
* c o n s e n t a di r i s p o n d e r e in tempo O (1) a query del tipo : ‘‘ quanti
* e l e m e n t i di $A$ sono c o m p r e s i nell ’ i n t e r v a l l o [a , b ]? ’ ’ ( per
* q u a l s i a s i $1 \ leq a \ leq b \ leq k$ )
*
* Version 0.1 del 2 0 0 9 / 1 1 / 0 9
* Autore : Moreno M a r z o l l a ( m a r z o l l a ( at ) cs . unibo . it )
*
* This file has been r e l e a s e d by the author in the Public Domain
*/
public class Intervalli {
int sum []; // sum [ i ] e ’ il numero di e l e m e n t i di A che sono minori
// o uguali di i , i =1 ,2 ,... k . sum [0] vale zero
int k ;
/* *
* P r e p r o c e s s a l ’ array A [] ( che puo ’ c o n t e n e r e e s c l u s i v a m e n t e
* valori interi c o mp r e s i nell ’ i n t e r v a l l o [1 ,... k ]) in modo da
* poter poi r i s p o n d e r e in tempo O (1).
*
* Questo metodo ha c o m p l e s s i t ’ Theta ( n + k ) , essendo n il numero di
* e l e m e n t i in A [].
*/
public Intervalli ( int A [] , int k )
{
this . k = k ;
int i ;
sum = new int [ k +1];
// I n i t i a l i z z a sum [] a zero
for ( i =0; i < k +1; ++ i )
sum [ i ] = 0;
// Calcola i valori si sum [] i t e r a n d o una sola volta sull ’ array A []
for ( i =0; i < A . length ; ++ i ) {
sum [ A [ i ]]++;
}
// Calcola le somme
for ( i =1; i < k +1; ++ i ) {
sum [ i ] = sum [i -1]+ sum [ i ];
}
4
}
/* *
* R e s t i t u i s c e il numero di e l e m e n t i di A [] c o m p r e s i tra a e b
* ( estremi inclusi ). Questo metodo ha c o m p l e s s i t ’ O (1).
*/
public int conta ( int a , int b )
{
// si assume che 1 <= a <= b <= k
return ( sum [ b ] - sum [a -1]);
}
public static void main ( String args [] )
{
int A [] = {1 , 2 , 3 , 1 , 3 , 2 , 2 , 4 , 2 , 3 , 1 , 3 ,
Intervalli interv = new Intervalli (A ,4);
System . out . println ( " Numero elementi in [1 ,2] =
System . out . println ( " Numero elementi in [2 ,3] =
System . out . println ( " Numero elementi in [3 ,4] =
}
2 , 2 , 4 , 1 , 1 , 1 , 2};
" + interv . conta (1 ,2));
" + interv . conta (2 ,3));
" + interv . conta (3 ,4));
}
3
L’attitudine al problema vs l’attitudine alla
soluzione
(problema 4.11 del libro di testo). Progettare un algoritmo che, dato in input
un array A[1..n] contenente n valori reali, restituisca in tempo O(n) un qualsiasi
numero che non appartenga ad A. Dimostrare che Ω(n) è un limite inferiore al
costo computazionale di questo problema.
Soluzione Visto che qui si richiede di individuare un numero qualsiasi che
non appartiene all’insieme dato, si può procedere nel modo seguente:
• Si calcola il minimo x dell’array (questa operazione ha costo Θ(n));
• Si restituisce come risultato il valore x−1, che sicuramente non appartiene
ad A.
È immediato che l’algoritmo cosı̀ fatto ha complessità Θ(n). Per quanto riguarda
il limite inferiore, è sufficiente considerare che per individuare un numero che non
appartenga al vettore dato, è sempre necessario esaminare ciascun elemento del
vettore almeno una volta. Se i valori non venissero tutti presi in considerazione,
ci sarebbe sempre la possibilità che il valore restituito possa essere presente tra
quelli non esaminati. Da ciò si conclude the Ω(n) è un limite inferiore alla
complessità di questo problema.
5
4
Verifica Min-Heap
Quesito presente nel primo parziale dell’Anno Accademico 2009/2010 Scrivere
un algoritmo efficiente per risolvere il seguente problema: dato un array A[1..n]
di n > 0 valori reali, restituire true se l’array A rappresenta un min-heap binario,
false altrimenti. Calcolare il costo computazionale nel caso pessimo e nel caso
ottimo dell’algoritmo proposto, motivando le risposte.
Soluzione
Una possibile soluzione è la seguente:
Algoritmo isMinHeap( Array A[1..n] )
for i:=1 to n do
if ( 2*i <= n && A[i] > A[2*i] ) then
return false;
endif
if ( 2*i+1 <= n && A[i] > A[2*i+1] ) then
return false;
endif
endfor
return true;
Si notino i controlli (2*i<=n) e (2*i+1<=n): tali controlli sono indispensabili per essere sicuri che i figli dell’elemento A[i] (rispettivamente A[2 ∗ i]
e A[2 ∗ i + 1]) siano effettivamente presenti nell’array. Se tali controlli non
sono effettuati, l’algoritmo è da considerarsi sbagliato in quanto può causare
l’indicizzazione di un array al di fuori dei limiti.
Il caso pessimo si ha quando il ciclo “for” viene eseguito interamente, ossia
quando l’array A[1..n] effettivamente rappresenta un min-heap. In questo caso
il costo è Θ(n). Il caso ottimo si verifica quando la radice A[1] risulta maggiore
di uno dei due figli (A[2] o A[3], se esistono). In questo caso il ciclo “for” esce
alla prima iterazione restituendo false, e il costo risulta O(1). In definitiva, si
può dire che il costo di questo algoritmo è O(n).
5
Modifica di Selection Sort
Quesito presente nel primo parziale dell’Anno Accademico 2009/2010 Considerare la seguente variante dell’algoritmo selectionSort():
Algoritmo mioSelectionSort(array A[1..n])
for k:=0 to n-2 do
m:=k+1;
for j:=k+2 to n do
if (A[j] <= A[m]) then
m := j;
endif
endfor
6
scambia A[k+1] con A[m];
endfor
Ricordiamo che un algoritmo di ordinamento è stabile se preserva l’ordinamento
relativo degli elementi uguali presenti nell’array da ordinare. L’algoritmo mioSelectionSort()
è un algoritmo di ordinamento stabile? In caso negativo, mostrare come modificarlo per farlo diventare stabile. Motivare le risposte.
Soluzione L’algoritmo mioSelectionSort() è quasi identico all’algoritmo
SelectionSort() descritto nel libro di testo, con la differenza che la condizione
stata scritta come if ( A[j] <= A[m] ) (usando l’operatore ≤ anziché =).
Questo fa sı̀ che mioSelectionSort non sia un algoritmo di ordinamento stabile, perché in caso di elementi uguali mette in prima posizione l’ultimo elemento
trovato. Per renderlo stabile sufficiente riscrivere l’if come if ( A[j] < A[m] )
7