Вы находитесь на странице: 1из 13

Módulo 4

Lectura 5
Unidad 5: Simulación

Taller de Algoritmos y Estructuras de Datos II


Paula Abruzzini / Nicolas Frontini
5.1- Números Aleatorios y
generadores

Introducción
Los números aleatorios tienen importantes aplicaciones en
programación, entre los que se incluye la criptografía, la simulación
y la comprobación de programas.

En muchas ocasiones en Computación se requiere el uso de números


aleatorios y sobre todo en los sistemas de simulación. Sin embargo, no es
tan fácil implementar buenos generadores de números aleatorios.

“La verdadera aleatoriedad es imposible de alcanzar en una computadora,


ya que los números obtenidos dependen del algoritmo empleado en su
generación y esto los convierte en aleatorios. Ahora bien, generalmente, es
suficiente producir números pseudo-aleatorios, esto es, números que
parecen aleatorios, en el sentido de que satisfacen muchas de las
propiedades que cumplen los números aleatorios.”1

Supongamos que necesitamos simular el lanzamiento de una moneda. Una


forma de hacerlo es examinar el reloj interno del sistema, ya que nos indica
el número de segundos de la hora actual. Si esta cantidad es par, podemos
retornar 0 (cara) y si es impar, podemos retornar 1 (cruz). El método de la
siguiente figura muestra la implementación en Java:

El problema es que esta estrategia no funciona correctamente si


necesitamos generar una secuencia de números aleatorios. En tal caso, lo
más probable es que se genere una secuencia de todo ceros o todo unos y
evidentemente esto dista mucho de ser aleatorio. Esto ocurre porque en un

1
Mark Allen Weiss, “Estructuras de Datos en Java”, Adisson Weisley - 2000 - S/D - Bs As

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 2


mismo segundo, se realizan varios cálculos para generar el número
aleatorio. Para probar lo expuesto, basta con ver que resultado nos da la
ejecución del método lanzarMondea unas 10 veces:

Así, verificamos que lo que se imprime por pantalla (consola), es una lista
de 10 números todos iguales (unos o ceros). Tampoco nos sirve si tomamos
los milisegundos en vez de los segundo, ya que el programa está haciendo
más cosas entre las llamadas al generador, la secuencia de números
generada no sería aleatorio, ya que el tiempo transcurrido entre las
llamadas al generador sería prácticamente idéntico en cada invocación del
programa, con lo que los resultados que se producirían al realizar distintas
ejecuciones serían demasiado parecidas.

La clase Random
Como se describe en “Procedimientos Numéricos en Lenguaje Java:
Diciembre de 2001”2, la clase Random proporciona un generador de
números aleatorios que es más flexible que la función estática random de la
clase Math.

Para crear una secuencia de números aleatorios tenemos que seguir los
siguientes pasos:

1. Proporcionar a nuestro programa información acerca de la


clase Random. Al principio del programa escribiremos la siguiente
sentencia: import java.util.Random;

2. Crear un objeto de la clase Random.

3. Llamar a una de las funciones miembro que generan un número


aleatorio.

4. Usar el número aleatorio.

Constructores

La clase dispone de dos constructores, el primero crea un generador de


números aleatorios cuya semilla es inicializada en base al instante de
tiempo actual.

2
Procedimientos Numéricos en Lenguaje Java: Diciembre de 2001
http://www.sc.ehu.es/sbweb/fisica/cursoJava/fundamentos/clases1/azar.htm

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 3


El segundo, inicializa la semilla con un número del tipo long.

El sufijo L no es necesario, ya que aunque 3816 es un número int por


defecto, es promocionado automáticamente a long.

Aunque no podemos predecir qué números se generarán con una semilla


particular, podemos sin embargo, duplicar una serie de números aleatorios
usando la misma semilla, es decir, cada vez que creamos un objeto de la
clase Random con la misma semilla obtendremos la misma secuencia de
números aleatorios. Esto no es útil en el caso de loterías, pero puede serlo
en el caso de juegos, exámenes basados en una secuencia de preguntas
aleatorias, las mismas para cada uno de los estudiantes, simulaciones que
se repitan de la misma forma una y otra vez, entre otros.

Funciones miembro

Podemos cambiar la semilla de los números aleatorios en cualquier


momento, llamando a la función miembro setSeed.

Podemos generar números aleatorios en cuatro formas diferentes:

genera un número aleatorio entero de tipo int

genera un número aleatorio entero de tipo long

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 4


genera un número aleatorio de tipo float entre 0.0 y 1.0, aunque siempre
menor que 1.0

genera un número aleatorio de tipo double entre 0.0 y 1.0, aunque siempre
menor que 1.0

Casi siempre usaremos esta última versión. Por ejemplo, para generar una
secuencia de 10 números aleatorios entre 0.0 y 1.0 escribimos:

Para crear una secuencia de 10 números aleatorios enteros comprendidos


entre 0 y 9 ambos incluidos escribimos:

(int) transforma un número decimal double en entero int eliminando la


parte decimal.

Comprobación de la uniformidad de los números aleatorios

Podemos comprobar la uniformidad de los números aleatorios generando


una secuencia muy grande de números aleatorios enteros comprendidos
entre 0 y 9 ambos inclusive. Contamos cuántos ceros aparecen en la
secuencia, cuántos unos, ... cuántos nueves, y guardamos estos datos en los
elementos de un array.

Primero creamos un array ndigitos de 10 de elementos que son enteros.

Inicializamos los elementos del array a cero.

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 5


Creamos una secuencia de 100000 números aleatorios enteros
comprendidos entre 0 y 9 ambos inclusive (véase el apartado anterior)

Si n sale cero suma una unidad al contador de ceros, ndigitos[0]. Si n sale


uno, suma una unidad al contador de unos, ndigitos[1], y así
sucesivamente.

Finalmente, se imprime el resultado, los números que guardan cada uno de


los elementos del array ndigitos

Observaremos en la consola que cada número 0, 1, 2...9 aparece


aproximadamente 10000 veces.

Secuencias de números aleatorios

En la siguiente porción de código, se imprime dos secuencias de cinco


números aleatorios uniformemente distribuidos entre [0, 1), separando los
números de cada una de las secuencias por un carácter tabulador.

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 6


Comprobaremos que los números que aparecen en las dos secuencias son
distintos.

En la siguiente porción de código, se imprimen dos secuencias iguales de


números aleatorios uniformemente distribuidos entre [0, 1). Se establece la
semilla de los números aleatorios con la función miembro setSeed.

Fuente: Pagina web:


http://www.sc.ehu.es/sbweb/fisica/cursoJava/fundamentos/clases1/azar.h
tm

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 7


5.2- Problema de Josephus

Contexto
En una simulación, la computadora emula el funcionamiento de un
sistema real y toma datos estadísticos.

El problema de Josephus es un problema teórico cuyo nombre se debe a


Flavio Josefo, un historiador que vivió en el siglo I. Según lo que cuenta
Josefo, él y 40 soldados se encontraban atrapados en una cueva, rodeados
de romanos y prefirieron suicidarse antes que ser capturados, para ello
decidieron que la suerte debía decir quién mataba a quién. Los últimos que
quedaron fueron él y otro hombre. Entonces convenció al otro hombre que
debían entregarse a los romanos en lugar de matarse entre ellos. Josefo
atribuyó su supervivencia a la suerte o al destino.

El problema plantea lo siguiente:

“Hay n personas paradas en un círculo esperando a ser ejecutadas. Después


de que ejecutan a la primera persona, saltean a k-1 personas y la persona
número k es ejecutada. Entonces nuevamente, saltean a k-1 personas y la
persona número k es ejecutada. La eliminación continúa alrededor del
círculo (que se hace cada vez más pequeño a medida que más personas son
eliminadas del mismo) hasta que sólo queda la última, que es liberada.” 3

El objetivo es escoger el lugar inicial en el círculo para sobrevivir (es el


último que queda), dados n y k.

El siguiente algoritmo en Java resuelve el problema de Josephus:

3
Problema de Flavio Josefo
http://es.wikipedia.org/wiki/Problema_de_Flavio_Josefo

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 8


El método josephus recibe como parámetros la cantidad de personas y la
constante k que indica la cantidad posiciones que hay que dejar hasta
eliminar a una persona.

En la primer parte del algoritmo se crea un arreglo con la cantidad de


personas y se inicializa cada posición del arreglo (posición en el que se
encuentra una persona) con un 1 indicando que la persona está viva.

Luego se inicializa el índice en 0 para indicar el comienzo del juego. Se itera


hasta que quede sólo una persona viva (persona > 1). Por cada eliminación,
se itera desde 0 hasta la constante k para saber quién es el próximo que se
debe eliminar. En este paso, hay que tener en cuenta que las posiciones con
personas eliminadas (circulo[índice] == 0) hay que saltearlas ya que no las
tenemos que contar. Luego de contar hasta k, marcamos la posición con un
0 indicando que esa persona se elimina y restamos la cantidad de personas
en una unidad para seguir con el juego. En cada incremento del índice
tenemos que verificar que el índice no supere el tamaño del arreglo; si esto
ocurre, debemos reiniciar el índice a 0 ya que consideramos que las
personas están ubicadas en círculo y que después de pasar por la última
persona, la cuenta sigue con el primer (arreglo circular). También es posible
implementarlo utilizando una lista doble vinculada circular.

Finalmente, el algoritmo retorna el arreglo con la única persona que


sobrevivió. Con esto podemos saber que si hay 20 personas y la constante k

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 9


es 45, la posición en la que deberíamos colocarnos para sobrevivir es la
número 19.

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 10


5.3- Soluciones

Alternativas
Un árbol binario equilibrado funcionará, pero no es imprescindible si
somos cuidadosos y construimos al principio un árbol binario de
búsqueda ordinario que no esté desequilibrado.

Para problema de Josephus, se puede obtener un algoritmo más eficiente si


utilizamos una estructura de datos que soporte el acceso al k-ésimo
elemento más pequeño (en tiempo logarítmico); esto nos permite
implementar cada ronda de pases con una única operación. Para lograr una
búsqueda en O(log N) deberíamos utilizar la estructura de árbol vista
anteriormente. De esta forma, si utilizamos un árbol binario de búsqueda,
podemos esperar un algoritmo de O(N log N).

La forma más simple de utilizar esta estructura es insertar los elementos


secuencialmente en el árbol binario de búsqueda de tal forma que nos
aseguremos que el árbol quede balanceado (equilibrado). El siguiente
método muestra cómo cargar los elementos de forma secuencial sin
desequilibrar el árbol:

El método cargarArbolBinario tiene como parámetro una lista de elementos


que son los que se pretenden cargar en el árbol. Para que el método cumpla
con el requerimiento de que se cargue un árbol balanceado, la lista tiene
que estar ordenada. De esta forma, el método cargarArbolBinario llama a
otro método, con el mismo nombre pero con otros parámetros: nodo, lista y

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 11


cantidad. Nodo representa el árbol en el que se van a insertar los elementos,
lista es la lista de elementos que se van a insertar y cantidad es la cantidad
de elementos que tiene la lista. El algoritmo toma el elemento del medio de
la lista y lo inserta en un nuevo nodo. Luego, asigna el nodo izquierdo y
derecho al valor que retorna la llamada recursiva a cargarArbolBinario. Esta
vez, se pasa como parámetro el nodo izquierdo y el nodo derecho para cada
llamada, así como también la primera mitad de la lista y la segunda mitad
de la lista.

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 12


Bibliografía Lectura 1
Mark Allen Weiss, “Estructuras de Datos en Java”, Adisson Weisley - 2000 -
S/D - Bs As

T. Cormen, C. Leiserson, R. Rivest, C.Stein, “Introduction to Algorithms,


2nd Edition.” McGraw Hill. 2002. Sun Press, Core Java Vol. 1 Fundamentals (a
proveer por la cátedra) Sun, Java 1.5 API Especification (a proveer por la cátedra)
O"Reilly, Eclipse y Eclipse Cookbook (a proveer por la cátedra)

www.uesiglo21.edu.ar

Taller de Algoritmos y estructura de Datos II | Abruzzini - Frontini | 13

Вам также может понравиться