Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Algoritmo Fishers
Algoritmo Fishers
10 idiomas
Artículo
Discusión
Leer
Editar
Ver historial
Herramientas
(Redirigido desde «Algoritmo Fisher-Yates»)
Historia[editar]
El algoritmo Fisher-Yates aparece documentado por primera vez por Ronald A.
Fisher y Frank Yates en el libro Statistical tables for biological, agricultural and
medical research.1 si bien su descripción era realizada con lápiz y papel.
Posteriormente otros autores (probablemente sin conocimiento previo de dicha
publicación) elaboraron el mismo algoritmo. Lincoln E. Moses y Robert V.
Oakford en Tables of Random Permutations2 y Durstenfeld en CACM-73(1964),
a quienes Knuth cita en su libro The Art of Computer Programming4 y que describe
como "Algoritmo P" (pags. 139-140 del Vol.2).
En la 'sabiduría popular' este algoritmo se conoce como el 'barajado del sombrero',
ya que la descripción que hicieron de él Fisher y Yates es la que se lleva a cabo
habitualmente cuando, por ejemplo, se hace una rifa (ver los detalles más abajo,
en la descripción del mismo).
Transcripción a la programación[editar]
Fue Durstenfeld quien primero hizo una transcripción en la forma de algoritmo
para usarse en un ordenador. La descripción de Fisher y Yates exige el uso de 2
matrices (en los trabajos de campo, es simplemente anotar en dos partes de un
papel, aunque bien podría reutilizarse borrando y rescribiendo en el mismo sitio),
mientras que Durstenfeld usa la propia matriz para llevar a cabo todo el algoritmo,
necesitando solo como memoria extra una variable temporal.
Descripción del algoritmo[editar]
La forma más simple de entenderlo es partir de la forma popular:
Algoritmo BarajadoAleatorio:
Entrada:
Un Array(0,1,2,3,4... Cantidad-1) de valores.
Salida:
El mismo array con sus valores en posiciones aleatorias.
Definición de variables:
Cantidad: Un entero que señala la cantidad total de ítems que tiene
el array.
k: Un entero, que rige la cuenta del bucle.
az: Un entero, elegido por una función Random en el rango 0-k
(nótese que k se va reduciendo).
tmp: Un entero, que ha de contener un valor para intercambiar
valores entre 2 posiciones.
Funciones auxiliares:
Tamaño: Una función que devuelve la cantidad de elementos que
contiene el array.
Random: Una función que devuelve un número aleatorio de un rango de
valores.
Cantidad = Tamaño(Array)
Recorrer con k desde Cantidad-1 hasta 1 Regresivamente
az = Random(entre 0 y k)
tmp = Array(az)
Array(az) = Array(k)
Array(k) = tmp
Siguiente
Algoritmo BarajadoAleatorio:
Las variables son las mismas del caso anterior, excepto, que en la
entrada en vez de un array,
se recibe una estructura (por ejemplo una colección, una lista
enlazada, un árbol, etc...)
Recorrer con k desde Cantidad-1 hasta 1 Regresivamente
az = Random(entre 0 y k)
Estruc.AñadirItem(Estruc.Item(az), Al Final)
Estruc.BorrarItem(az)
Siguiente
Es interesante observar, en este caso, que los ítems al añadirlos al final, lo hacen
a la derecha del añadido anterior, es decir tal como describieron Fisher y Yates. Si
se prefiere conservar un añadido a la izquierda del previo añadido (tal como en el
caso mostrado del array), debe cambiarse la línea de añadido por la siguiente:
Estruc.AñadirItem(Estruc.Item(az), En Posición k)
Variantes[editar]
1 El algoritmo, presenta algunas variantes. De hecho es bastante fácil
que al tratar de implementar dicho algoritmo se acabe haciendo este
otro. Cuya particularidad más destacable, es que siempre que se baraja
se elige de nuevo entre todos los existentes (es como si el sombrero
tuviera posiciones donde se coloca cada uno cuando se meten, y tras
barajar el elemento sacado del sombrero se volviera a introducir de
nuevo y se sacaran tantos como elementos hay en el sombrero y
finalmente se expusiera el orden en que los elementos aparecen en el
sombrero):
Recorrer con k desde 0 hasta Cantidad - 1
az = Random(entre 0 y Cantidad-1)
tmp = Array(az)
Array(az) = Array(k)
Array(k) = tmp
Siguiente
tmp = Array(az)
Array(az) = Array(k)
Array(k) = tmp
Siguiente
Fin = (Tamaño(Array) \ 2) - 1
Recorrer con k desde 0 hasta Fin <--- Solo recorre la mitad.
az1 = Random(entre 0 y Cantidad-1)
az2 = Random(entre 0 y Cantidad-1)
Variables nuevas:
Series: Es una estructura acorde para contener arrays donde cada array
mantiene una
permutación distinta de la serie (un árbol de arrays, otro
array de arrays, etc...).
Veces: Es un contador, para barajar el orden de los arrays en la
estructura series,
cada vez que alcanza cierto valor, para favorecer la
aleatoriedad.
X: Un valor límite que controla el límite de veces. Este valor es
dependiente
de la cantidad de permutaciones
Funciones accesorias
Permutaciones: Sería una función de combinatoria, que devuelve una
estructura de arrays,
con todas las series posibles, donde cada array contiene una
serie distinta.
Barajar: Es un algoritmo de barajado aleatorio (como los descritos),
con la particularidad
de que cambiaría los punteros de los índices del array, en vez
del contenido.
--- Series Es precalculada, (y de nuevo cada vez que cambiare la
cantidad de elementos).
Series = Permutaciones(Cantidad)
Función: BarajarAleatorio
veces = veces + 1
si veces = x entonces <--- 'x' es un valor que se establece en
función
de la cantidad de elementos del array.
Barajar(Series)
veces = 0
Fin si
az = Random(entre 0 y Cantidad-1)
Array = Series(az)
Devolver Array
Fin función
tmp = Array(az)
Array(az) = Array(k)
Array(k) = tmp
Siguiente
Cantidad = Tamaño(Array)
Si se pide OmitirUno entonces
Cantidad = Cantidad - 1
Fin si
Recorrer con k desde Cantidad-1 hasta 1 Regresivamente
az = Random(entre 0 y k-1)
tmp = Array(az)
Array(az) = Array(k)
Array(k) = tmp
Siguiente
Variaciones parametrizadas[editar]
Hay algunas variaciones que pueden ser aplicadas a todas las implementaciones
comentadas hasta el momento, bien que pueda variar el código exacto que deba
añadirse o modificarse en cada una. Implementar estas variaciones, implica añadir
parámetros en las funciones para que al invocarlas puede elegirse el valor
necesario para que cumpla la misión encomendada.
Una variación aplicable a todas las implementaciones, consiste en hacer
un barajado dejando 1 elemento de ellos (o varios, seguidos) sin
barajar, es decir quedando en la misma posición. El modo más sencillo
de llevarlo a efecto es mentir al algoritmo en cuestión haciéndole
creer que tiene un elemento menos, así baraja todos (los que el
algoritmo en cuestión baraje) menos uno (o esos varios). Tal
circunstancia puede reclamarse en un parámetro opcional en la llamada
a la función. He aquí el pseudocódigo que se podría añadir a cualquiera
de ellos, detrás se pone la implementación de Durstenfeld, para apreciar
lo que implica (más arriba en Sattolo, se detalla un caso, que hace uso
de esta variación, para generar series cíclicas pero diferente a la
previa).
Cantidad = Tamaño(Array)
Si se pide OmitirUno entonces <--- OmitirUno sería aquí un parámetro
opcional de tipo buleano
Cantidad = Cantidad - 1
Fin si
Recorrer con k desde Cantidad-1 hasta 1 Regresivamente
az = Random(entre 0 y k)
tmp = Array(az)
Array(az) = Array(k)
Array(k) = tmp
Siguiente
La invocación a una función que implementa esta variación en el
algoritmo sería:
Llamada a la función BarajarAleatorio(Array, Durstenfeld, OmitirUno)
Entrada:
Un Array(0,1,2,3,4... Cantidad-1) de valores.
Grupo: un entero que señala cada cuantos se evita un barajado.
Salto: un entero que indica cuantos seguidos quedan sin barajar.
Cantidad = Tamaño(Array)
Fin = (Cantidad \ 2) - 1
Recorrer con k desde 0 hasta Fin <--- Solo recorre la mitad.
Si k es congruente con Grupo entonces ---> congruente con, refiere a
la operación módulo
(que se traduce como: si ((k mod grupo) = 0) luego
k = k + salto
En otro caso
az1 = Random(entre 0 y Cantidad-1)
az2 = Random(entre 0 y Cantidad-1)
tmp = Array(az1)
Array(az1) = Array(az2) <---- 'k' no se refiere nunca a la
posición de un elemento en el array de esta variante.
Array(az2) = tmp
Fin si
Siguiente
La invocación a una función que implementa esta variación en el
algoritmo sería:
Llamada a la función BarajarAleatorio(Array, Semibarajado, Grupo=13,
Salto=3)
Grupos = Grupos - 1
Recorrer con J desde (Grupos - 1) hasta 1 Regresivamente
Az = Random(entre 0 y J)
Si Az es distinto de' J entonces <--- si el grupo al azar es el mismo
del
bucle, no hace
falta intercambiarlo.
p = Az * Bloque
n = Az * Bloque
Recorrer con k desde n hasta n + Bloque - 1
tmp = Array(p)
Array(p) = Array(k)
Array(k) = tmp
p = p + 1
Siguiente
Fin si
Siguiente
Y la invocación a la función sería así:
Llamada a la función BarajarAleatorio(Array, BarajadoEnBloques,
Bloque=4)
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
24 25 26 27
24 25 26 27 20 21 22 23 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
16 17 18 19
16 17 18 19 00 01 02 03 20 21 22 23 24 25 26 27 04 05 06 07 08 09 10 11
12 13 14 15
00 01 02 03 24 25 26 27 12 13 14 15 16 17 18 19 04 05 06 07 08 09 10 11
20 21 22 23
04 05 06 07 16 17 18 19 20 21 22 23 24 25 26 27 12 13 14 15 00 01 02 03
08 09 10 11
----> aquí va el inserto que se señala unos párrafos más abajo <---
' Barajar los elementos internos del que ahora es el último bloque.
Min = ((Grupos - 1) * Bloque)
Recorrer con k desde Cantidad - 1 hasta Min Regresivamente <---- el
barajado es método Durstenfeld,
en este
ejemplo, pero podría ser otro
Az = Random(entre Min y k) <---- Min: limita el rango inferior en
vez de 0
tmp = Array(k)
Array(k) = Array(Az)
Array(Az) = tmp
Siguiente
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
24 25 26 27 <---entrada
04 05 06 07 20 21 22 23 08 09 10 11 24 25 26 27 12 13 14 15 00 01 02 03
19 16 17 18
19 16 17 18 04 05 06 07 24 25 26 27 08 09 10 11 20 21 22 23 00 01 02 03
14 15 13 12
04 05 06 07 24 25 26 27 20 21 22 23 19 16 17 18 14 15 13 12 08 09 10 11
02 01 00 03
02 01 00 03 14 15 13 12 08 09 10 11 20 21 22 23 19 17 16 18 07 04 07 05
26 25 24 27
14 15 12 13 02 08 09 10 11 01 00 03 19 17 16 18 07 04 07 05 20 21 22 23
25 27 24 26 <--- el último
bloque se intercambia
consigo mismo esta vez.
Usos[editar]
Como ya se ha dicho en el encabezado del artículo, es el algoritmo que se usa
típicamente para barajar en los juegos de azar (o alguna de sus variantes).
Se usa también en los modelos estadísticos cuando se requiere simular un origen
de datos aleatorio. Un caso práctico, para probar la eficiencia de los algoritmos de
ordenamiento. hacer simulaciones del clima, etc...
También se usa en sistemas y programas donde se desea usar una sola vez cada
elemento de la serie pero de una forma aleatoria, sin recurrir a una estructura
adicional de almacenamiento. Se provee un ejemplo al detalle, sobre las listas de
reproducción de canciones.
Reproducción aleatoria de los elementos de una lista[editar]
El presente algoritmo, resuelve también este caso, sin la necesidad de recurrir a
una segunda estructura donde mover los elementos retirados de la primera.
Imaginemos el caso más corriente, una lista de canciones de la que se desea
reproducir cada canción una sola vez, pero que lo hagan en orden aleatorio.
Inicialmente se tendrá una lista con las rutas de las canciones, lista que conviene
dejar intacta (no intercambiar sus elementos). Se usa un array cuyo contenido
(inicialmente) son los índices de las canciones en la lista. Si el orden es aleatorio,
se manda barajar el array, si el orden solicitado es el original, se reasigna a cada
índice el valor del índice de la lista (el orden natural correlativo). La reproducción
finalmente usará como índice, el valor de dicho array. En vez de invocar:
Reproducir(Ruta(k)), se invocará: Reproducir(Ruta(Array(k))), así la lista original,
no necesita ser tocada ni duplicada para mantener el orden original (es más rápido
en memoria asignar valores numéricos, que textos).
0 1 2 3 4 5 6 : 0 1 2 3 4 5 6 :
012
6 0-6 4 0 1 2 3 6 5 :4
3 4 5 6 :
0 1 2 3 5 1 2 3
5 0-5 0
6 5 : 4 6 : 0 4
6 1 2 3 : 5 0 6 1 3 : 2 5 0
3 0-3 2
4 4
6 1 3 : 2 5 0 6 1 : 3 2 5 0
2 0-2 2
4 4
6 1 : 3 2 5 0 1 : 6 3 2 5 0
1 0-1 0
4 4
1 : 6 3 2 5 0 1 6 3 2 5 0
- 0-0 -
4 4 :
:0123456 :0123456
: 0 1 2 3 5 : 1 2 3
0 0-6 5
4 5 6 4 0 6
5 : 1 2 3 4 0 5 3 : 2 1 4 0
1 1-6 3
6 6
5 3 : 2 1 4 5 3 6 : 1 4
2 2-6 6
0 6 0 2
5 3 6 : 1 4 0 5 3 6 4 : 1 0
3 3-6 4
2 2
536 536
4 4-6 5
4 : 1 0 2 4 0 : 1 2
5364 5364
5 5-6 6
0 : 1 2 0 2 : 1
53640 53640
- 6-6 -
2 : 1 2 1 :
Tabla paso a paso (implementación Fisher-Yates original)[editar]
Para la descripción del algoritmo original, empleando para ello, lápiz y papel, es
decir tachando de la lista en una línea y escribiendo los resultados en una nueva
línea, que será la lista resultante. Esto supone simular 2 estructuras, una que
contiene la lista original de la que se extraen y otra donde se insertan. Nótese las
siguientes cuestiones para este ejemplo:
Rang
Ciclo Azar Antes Después Resultado
o
012345
0123456 0123456
6
0123
0 0-6 5 0 1 2 3 4 5 6 5
4 5 6
01 01
1 0-5 3 5 3
2 3 4 5 6 2 3 4 5 6
01 01
2 0-4 4 5 3 6
2 3 4 5 6 2 3 4 5 6
0 1 2 3 4 5 0 1 2 3 4 5
3 0-3 3 5 3 6 4
6 6
0 1 2 3 4 5 53640
6 0-0 - 0 1 2 3 4 5 6
6 2 1
Cicl
Rango Azar Antes Después
o
012345678
0 1 2 3 4 5 6 7 8 9 :
9 :
0 1 2 3 4 5 6 7 0 1 2 3 4 5 9 7
9 0-8 6
8 9 : 8 : 6
0 1 2 3 4 5 9 0 1 2 3 8 5 9
8 0-7 4
7 8 : 6 7 : 4 6
0 5 2 3 7 : 9 1 8 4 0 5 2 7 : 3 9 1 8 4
4 0-3 3
6 6
7 5 2 : 0 3 9 1 8 4 7 2 : 5 0 3 9 1 8 4
2 0-1 1
6 6
7 2 : 5 0 3 9 1 8 4 2 : 7 5 0 3 9 1 8 4
1 0-0 0
6 6
- - - 2 : 5 0 3 9 1 8 4 6 : 2 7 5 0 3 9 1 8 4 6
98765432
0123456789 0123456789
1
6 4 4 1 1 3 0 1 2 7 5 0 3 9 1 8 4
1 0123456789
0 6
0 3 6 4 1 3 2 0 9 8 6 5 4 7 3 1 0
2 2750391846
0 2
4 6 0 3 2 1 1 1 7 1 0 2 8 6 5 9 3
3 9865473102
0 4
7 6 4 5 4 0 2 1 2 3 1 0 7 4 6 8 5
4 7102865934
0 9
0 7 4 0 2 0 0 1 4 0 3 5 6 1 9 7 8
4 2310746859
0 2
2 3 2 5 4 1 0 0 9 7 8 4 0 6 1 2 5
6 4035619782
0 3
3 3 4 1 3 0 0 1 8 6 1 2 9 5 7 0 3
7 9784061253
0 4
8 2 5 2 1 3 1 1 7 8 9 0 2 6 4 5 1
8 8612957034
0 3
5 5 6 5 0 2 2 0 8 0 5 2 9 7 1 4 3
9 7890264513
0 6
4 3 2 2 1 0 0 1 1 3 7 6 8 0 4 5 2
10 8052971436
0 9
3 3 1 2 1 2 1 0 2 8 1 0 4 5 7 3 9
11 1376804529
0 6
4 3 0 4 2 3 1 1 5 3 7 8 9 1 6 2 0
12 2810457396
0 4