Esplora E-book
Categorie
Esplora Audiolibri
Categorie
Esplora Riviste
Categorie
Esplora Documenti
Categorie
Vincenzo La Spesa
4 marzo 2010
v1.2
Indice
1 Algoritmo iterativo per generare permutazioni in ordine lessicografico 2
Riferimenti bibliografici 11
http://thedarshan.wordpress.com/
1
1 Algoritmo iterativo per generare permutazioni in ordine lessicografico
L’algoritmo seguente è probabilmente il più semplice algoritmo iterativo per listare le
permutazioni di un insieme di elementi, analizza semplicemente la permutazione attuale
per ricavarne la prossima basandosi sul fatto che le permutazioni devono seguire un
ordinamento lessicografico (nel caso numerico qui analizzato ogni permutazione deve
essere la minima permutazione maggiore di quella corrente)
considerando la permutazione come un vettore di n elementi numerici l’algoritmo
determina la permutazione successiva nel seguente modo:
1. a partire dall’ultimo elemento scorre l’array cercando il primo elemento preceduto
da un elemento minore
2. se non esiste si è giunti all’ultima permutazione possibile e l’algoritmo deve termi-
nare
3. se esiste ricomincia a scorrere il vettore dalla fine cercando il primo elemento
maggiore all’elemento trovato al punto1
4. si invertono gli elementi trovati al punto1 e al punto3
5. se lo scambio non è avvenuto tra elementi di livello minimo (l’ultimo e il penultimo)
si ripristina l’ordinamento interno invertendo le posizioni degli elementi che si
trovano tra l’elemento trovato al punto1 e quello trovato al punto3
l’algoritmo è molto semplice e molto generico ma non è efficiente, infatti il fatto di dover
analizzare a ogni iterazione la struttura attuale del vettore ne incrementa di molto la
complessità.
Listing 1: Algoritmo semplice
public boolean p r o s s i m a ( ) {
i nt i = l e n − 1 ;
while ( i >0 && ( e l e m e n t i [ i − 1 ] >= e l e m e n t i [ i ] ) ) i −−;
i f ( i ==0)return f a l s e ;
i nt j = l e n ;
// ( i −1) è l ’ e l e m e n t o t r o v a t o con i l c i c l o p r e c e d e n t e
while ( e l e m e n t i [ j − 1 ] <= e l e m e n t i [ i − 1 ] ) j −−;
swap ( i − 1 , j − 1 ) ;
i ++;
j = len ;
// i n v e r t o g l i e l e m e n t i t r a i due e l e m e n t i s c a m b i a t i
while ( i < j ) {
swap ( i − 1 , j − 1 ) ;
i ++;
j −−;
}
return true ;
}
2
2 Algoritmo ricorsivo per generare permutazioni
L’uso di un algoritmo ricorsivo per la generazione di permutazioni non è particolarmen-
te conveniente perche non permette di generare una permutazione per volta, e inoltre
l’approccio iterativo è di persè più veloce perche non usa memoria di stack, detto questo
ho deciso di includere comunque questo algoritmo come esempio “didattico”.
L’implementazione ricorsiva è una tipica applicazione dell’approccio divide et impera
3
3 Algoritmo degli scambi semplici (Plain changes - Johnson-Trotter)
L’algoritmo degli scambi semplici è stato ideato nel diciassettesimo secolo in Inghilterra
da parte dei suonatori di campane che avevano sviluppato il buffo passatempo di suonare
le campane secondo tutte le permutazioni possibili. 1
Le prime traccie scritte di questo algoritmo risalgono al 1653 ed è trattato in maniera
estensiva nel libro Tintinnalogia del 1668 che gli dedica addirittura 60 pagine... Le prime
implementazioni informatiche documentate risalgono invece al 1962 ad opera di H. F.
Trotter e al 1963 con S.M. Johnson ed è per questo che l’algoritmo è noto anche come
l’algoritmo di Johnson-Trotter.
L’algoritmo opera facendo in modo che solo una coppia venga scambiata ad ogni
permutazione e che la coppia sia sempre formata da elementi adiacenti.
L’idea da cui nasce l’algoritmo è che si possano generare le permutazioni di un vettore
di n elementi scegliendo dei sottovettori di n-1 elementi e facendo scorrere l’n-esimo
elemento all’interno dei sottovettori.
Quando l’n-esimo ha percorso l’intero sottovettore, si calcola la prossima permuta-
zione del sottovettore e si fa scorrere l’n-esimo elemento in senso contrario.
Per implementare questa procedura si usa un secondo vettore che contiene le direzioni
di mobilità possibili (il vettore D) che vengono settate a 1 (mobile a destra) o a -1 (mobile
a sinistra).
Un singolo elemento è mobile soltanto se nella direzione della sua mobilità (vettore D)
l’elemento successivo è inferiore dell’elemento corrente (in particolare il primo elemento
non è mai mobile a sinistra e l’ultimo non è mai mobile a destra).
Si usa inoltre un vettore degli scambi (vettore C) il cui n-esimo elemento rappresenta
il numero di elementi minori di n che stanno alla sua destra. Confrontando le variazioni
del vettore C con la direzione di mobilità contenuta nel vettore D si può ottenere lo
scambio da eseguire per arrivare alla permutazione successiva.
a questo punto l’algoritmo agisce in questo modo:
1
altre informazioni sull’algoritmo e sulla sua origine si trovano in
Knuth, Donald (2005), ”Volume 4 Fascicle 2, Generating All Tuples and Permutations”, The Art of
Computer Programming , Addison-Wesley, paragrafo 2.7.1.2, ISBN 0-201-85393-0
4
Listing 3: Implementazione del Plain Changes
/
elemen t i []= i l v e t t o r e d e g l i elemen t i
c [ ] = i l v e t t o r e d e g l i scambi
d []= i l v e t t o r e d e l l e d i r e z i o n i
n=dimenzione d e l v e t t o r e
j=s c o r r e i l v e t t o r e d a l l a f i n e a l l ’ i n i z i o
/
public void l i s t a l l ( ) {
i nt n=t h i s . l e n ;
do{
f l a g=true ;
System . out . p r i n t l n ( A r r a y u t i l . dump( e l e m e n t i ) ) ;
j=n−1;
s =0;
do{
q=c [ j ]+d [ j ] ;
i f ( q==1 && j ==0)return ;
i f ( q>=0 && q !=( j +1)){
A r r a y u t i l . swap ( e l e m e n t i , j −c [ j ]+ s , j −q+s ) ;
c [ j ]=q ;
f l a g=f a l s e ;
}else{
i f ( q==j +1) s++;
i f ( q<0 | | q==j +1){
d [ j ]=−d [ j ] ;
j −−;
}
}
} while ( f l a g ) ;
} while ( true ) ;
}
5
4 Determinare una specifica permutazione dall’insieme delle permuta-
zioni
Potrebbe essere necessario determinare una singola permutazione dall’insieme delle per-
mutazioni ordinate e inutile determinarle tutte.
In questo caso si deve evitare di generare tutte le permutazioni fino a quella cercata in-
quanto si vuole determinare una specifica permutazione in tempo costante o quantomeno
proporzionale al numero di elementi del dominio.
La soluzione risiede nell’uso dei factoradic
4.1 I Factoradic
Di solito un numero viene rappresentato come un polinomio di potenze della base
inquanto le potenze dei primi n numeri costituiscono una base che permette di rappre-
sentare univocamente un numero con un polinomio.
Anche i fattoriali dei primi n numeri costituiscono una base , quindi è possibile
rappresentare univocamente un numero come polinomio dei fattoriali della base
1936 = 2 · 6! + 4 · 5! + 0 · 4! + 2 · 3! + 2 · 2! + 0 · 1!
il codice 0110 indica che, partendo dalla permutazione di base, si deve invertire
l’elemento 2 e poi l’elemento 3 ottenendo la permutazione [1,3,4,2]
6
n Factoradic Permutazione Permutazione Vettore degli Vettore delle
(lessicografica) (plain changes) scambi direzioni
7
4.2.1 Algoritmi per la generazione di un Factoradic e della permutazione associata
l’algoritmo di generazione è molto semplice: le singole componenti vengono calcolate con
l’operatore modulo
E’ lo stesso tipo di algoritmo che si usa per convertire un numero decimale in una base
diversa, si va dividendo il numero per la base e i resti delle divisioni vanno a costituire il
numero nella base di arrivo, solo che in questo caso la base non è costante ma decresce.
8
4.3 Generazione di permutazioni casuali con i factoradic
Dando come input alla funzione precedente un numero casuale compreso nel numero di
permutazioni si ottiene ovviamente una permutazione casuale.
9
6 Generare permutazioni casuali (algoritmo di Fisher-Yates o Knuth
shuffle)
Originariamente sviluppato come algoritmo da eseguire “a mano” da Fisher e Yates nel
1938 venne adattato all’uso automatico da Richard Durstenfeld nel 1964 e reso famoso
da Donald E. Knuth anche se probabilmente questi ultimi lo ricrearono da zero vista la
sua semplicità.
L’algoritmo opera semplicemente scorrendo il vettore e scambiando gli elementi con
un altro elemento il cui indice è scelto casualmente. Se la scelta dei numeri casuali è
corretta ogni permutazione ha la stessa probabilità di essere generata.
Listing 8: Shuffling
public s t a t i c i nt [ ] s h u f f l e ( i nt [ ] e l e m e n t i ) {
Random r = new Random ( ) ;
i nt k , n ;
for ( n = e l e m e n t i . l e n g t h −1; n >= 0 ; n−−)
{
k = r . n e x t I n t ( n + 1 );
A r r a y u t i l . swap ( e l e m e n t i , k , n ) ;
}
return e l e m e n t i ;
}
10
L’unico modo per evitare il bias è adattare generatore a generare numeri nel range
corretto, questa soluzione è implementata in Java che usa il generatore di Lehmer.
La descrizione dei generatori di numeri casuali in ogni caso non è lo scopo di questo
articolo, per ulteriori approfondimenti sul generatore di Lehmer rimando al solito testo
di Knuth.
Riferimenti bibliografici
[1] Donald E. Knuth The Art of Computer Programming, Volume 2: Seminumerical
Algorithms, section 3.2.1
[2] Donald E. Knuth (2005), The Art of Computer Programming, Volume 4 Fascicle 2,
Generating All Tuples and Permutations ,Addison-Wesley, paragrafo 2.7.1.2, ISBN
0-201-85393-0
[3] http://thedarshan.wordpress.com/2009/06/30/un-semplice-algoritmo-iterativo-per-
listare-le-permutazioni-di-un-insieme-di-elementi/
[4] http://thedarshan.wordpress.com/2010/02/18/algoritmo-degli-scambi-semplici-
plain-changes-johnson-trotter/
[5] http://thedarshan.wordpress.com/2009/09/19/determinare-una-specifica-
permutazione-dallinsieme-delle-permutazioni/
[6] http://thedarshan.wordpress.com/2009/09/24/ricavare-il-numero-di-una-
permutazione-ranking/
[7] http://thedarshan.wordpress.com/2009/06/30/un-semplice-algoritmo-iterativo-per-
listare-le-permutazioni-di-un-insieme-di-elementi/
11