Dardos, dados y monedas: algoritmos de distribución discreta


Una vez le hice una pregunta a Stack Overflow sobre la estructura de datos para hacer trampa en los dados . En particular, estaba interesado en la respuesta a esta pregunta: “Si tenemos un hueso n-facet, cuya cara tengo una probabilidad de caerse p i . ¿Cuál es la estructura de datos más efectiva para simular los rollos de un hueso así?

Esta estructura de datos se puede usar para muchas tareas. Por ejemplo, puede usarlo para simular tiradas hexadecimales honestas, asignando probabilidad  frac16 cada lado del hueso, o para simular una moneda justa imitando un hueso bilateral, la probabilidad de que se caiga de cada lado es igual a  frac12 . También puede usar esta estructura de datos para simular directamente la suma de dos huesos hexagonales honestos creando un hueso de 11 lados (con caras 2, 3, 4, ..., 12), cada una de las cuales tiene un peso de probabilidad correspondiente a los rollos de dos huesos honestos. Sin embargo, también puede usar esta estructura de datos para simular huesos de trampa. Por ejemplo, si juegas a los dados con un hueso, que, como sabes, no es completamente honesto, entonces puedes usar esta estructura de datos para simular muchos rollos de hueso y analizar la estrategia óptima. También puede intentar simular una rueda de ruleta igualmente imperfecta.

Si va más allá de los juegos, puede aplicar esta estructura de datos en la simulación de robots cuyos sensores tienen niveles de falla conocidos. Por ejemplo, si un sensor de rango tiene un 95% de probabilidad de devolver el valor correcto, un 4% de probabilidad de un valor demasiado pequeño y un 1% de probabilidad de un valor demasiado alto, entonces puede usar esta estructura de datos para simular la lectura de lecturas del sensor generando un resultado aleatorio y simulando la lectura del sensor resultado.

La respuesta que recibí en Stack Overflow me impresionó por dos razones. En primer lugar, en la solución se me recomendó utilizar una técnica poderosa llamada método alias , que, con ciertas suposiciones razonables sobre el modelo de la máquina, es capaz, después de una simple etapa de preparación preliminar, de simular rollos óseos con el tiempo O(1) . En segundo lugar, me sorprendió aún más que este algoritmo se conociera desde hace décadas, ¡pero nunca lo he conocido! Dado el tiempo computacional que se dedica a la simulación, uno esperaría que esta técnica sea mucho más ampliamente conocida. Unas pocas consultas en Google me dieron mucha información sobre esta técnica, pero no pude encontrar un solo sitio donde se uniera una comprensión intuitiva y una explicación de esta técnica.

Este artículo es mi intento de dar una breve descripción de los diferentes enfoques para simular el engaño óseo, desde técnicas simples y poco prácticas hasta un método de alias muy optimizado y efectivo. Espero poder transmitir varias formas de comprender intuitivamente la tarea y cómo cada una de ellas enfatiza algún aspecto nuevo de simular un hueso tramposo. Mi objetivo para cada enfoque es estudiar una idea motivadora, un algoritmo básico, prueba de fidelidad y análisis del tiempo de ejecución (en términos de tiempo requerido, memoria y aleatoriedad).

Entrada


Antes de pasar a los detalles específicos de las diversas técnicas, primero estandaricemos la terminología y la notación.

En la introducción del artículo, utilicé el término "hueso de engaño" para describir un escenario generalizado en el que hay un conjunto finito de resultados, cada uno de los cuales tiene una probabilidad. Formalmente, esto se denomina distribución de probabilidad discreta , y la tarea de simular un hueso de engaño se llama muestreo de una distribución discreta .

Para describir nuestra distribución de probabilidad discreta (hueso tramposo), asumiremos que tenemos un conjunto de n probabilidades p0,p1,...,pn1 relacionado con los resultados 0,1,...,n1 . Aunque los resultados pueden ser cualquiera (águila / cruz, números en huesos, colores, etc.), por simplicidad consideraré el resultado como algún tipo de número real positivo correspondiente a un índice dado.

Trabajar con números reales en una computadora es el "área gris" de la informática. Hay muchos algoritmos rápidos, cuya velocidad se proporciona únicamente por la capacidad de calcular la función de piso de un número real arbitrario en un tiempo constante, y las imprecisiones numéricas en la representación de números de coma flotante pueden destruir completamente algunos algoritmos. Por lo tanto, antes de comenzar cualquier discusión sobre algoritmos que funcionan con probabilidades, es decir, al ingresar al mundo oscuro de los números reales, debo aclarar lo que una computadora puede y no puede hacer.

De aquí en adelante, asumiré que todas las siguientes operaciones se pueden realizar en tiempo constante:

  • Además resta, multiplicación, división y comparación de números reales arbitrarios . Tendremos que hacer esto para manipular las probabilidades. Esto puede parecer una suposición audaz, pero si suponemos que la precisión de cualquier número real está limitada por algún polinomio del tamaño de palabra de la máquina (por ejemplo, un doble de 64 bits en una máquina de 32 bits), pero no creo que sea demasiado irrazonable.
  • Generación de un número real uniforme en el intervalo [0, 1). Para simular aleatoriedad, necesitamos alguna fuente de valores aleatorios. Supongo que podemos generar un número real de precisión arbitraria en tiempo constante. Esto supera con creces las capacidades de una computadora real, pero me parece que para los propósitos de esta discusión esto es aceptable. Si aceptamos sacrificar una fracción de la precisión al decir que un doble IEEE-754 arbitrario está en el intervalo [0, 1], entonces realmente perderemos precisión, pero el resultado probablemente será lo suficientemente preciso para la mayoría de las aplicaciones.
  • Cálculo del piso entero (redondeando hacia abajo) de un número real. Esto es aceptable si suponemos que estamos trabajando con IEEE-754 doble, pero en general, tal requisito para una computadora no es factible.

Vale la pena hacer la pregunta: ¿es razonable suponer que podemos llevar a cabo todas estas operaciones de manera efectiva? En la práctica, raramente usamos probabilidades indicadas con un nivel de precisión tal que el error de redondeo inherente en el IEEE-754 doble pueda causar serios problemas, por lo que podemos cumplir con todos los requisitos anteriores simplemente trabajando exclusivamente con IEEE doble. Sin embargo, si estamos en un ambiente donde la probabilidad indica con precisión cómo racional número de alta precisión, restricciones similares pueden ser irrazonable.

Simulación ósea honesta


Antes de pasar al caso más general de lanzar un hueso de engaño arbitrario, comencemos con un algoritmo más simple que servirá como un bloque de construcción para los siguientes algoritmos: simular un hueso honesto con cara n. Por ejemplo, los dados hexagonales honestos al jugar Monopolio o Riesgo, o lanzar una moneda honesta (dados de doble cara), etc. pueden ser útiles para nosotros.

Para este caso particular, existe un algoritmo simple, elegante y efectivo para simular el resultado. El algoritmo se basa en la siguiente idea: supongamos que podemos generar números reales realmente aleatorios, distribuidos uniformemente en el intervalo [0,1) . Este intervalo puede ilustrarse de la siguiente manera:


Ahora si queremos renunciar n hueso facetado, entonces una forma es dividir el intervalo [0,1) en n áreas de igual tamaño, cada una de las cuales tiene una longitud  frac1n . Se ve así:


A continuación, generamos un número real elegido al azar en el intervalo [0,1) eso seguramente cae en una de estas pequeñas áreas. A partir de esto, podemos calcular el resultado del giro del hueso al observar el área en la que cayó el número. Por ejemplo, si nuestro valor seleccionado al azar cayó en este lugar:


entonces podemos decir que 2 cayeron sobre el hueso (si suponemos que los bordes del hueso están indexados desde cero).

Es gráficamente fácil ver qué región tiene un valor aleatorio, pero ¿cómo codificamos esto en un algoritmo? Y aquí aprovechamos el hecho de que este es un hueso honesto. Dado que todos los intervalos son del mismo tamaño, a saber  frac1n , entonces podemos ver cuál es el mayor valor i es tal que  fracin no más que un valor generado aleatoriamente (llamemos a este valor x). Puede notar que si queremos encontrar el valor máximo, tal que  fracin lex , entonces esto es similar a encontrar el valor máximo n tal que i lexn . Pero esto, por definición, significa que i= lfloorxn rfloor , el entero positivo más grande no es mayor que xn. Por lo tanto, esto nos lleva a este algoritmo de simulación ósea honesto (muy simple) honesto:

Algoritmo: simulación ósea honesta


  1. Generar un valor aleatorio distribuido uniformemente x en el rango [0,1) .
  2. Volver  lfloorxn rfloor .

Dados nuestros supuestos anteriores sobre los cálculos, este algoritmo se ejecuta a tiempo O(1) .

Se pueden sacar dos conclusiones de esta sección. Primero, podemos dividir el intervalo [0,1) en parte para que un número real aleatorio distribuido uniformemente en este intervalo se reduzca naturalmente a una de las muchas opciones discretas disponibles para nosotros. En el resto de este artículo, explotaremos activamente esta técnica. En segundo lugar, puede ser difícil determinar a qué intervalo específico pertenece un valor aleatorio, pero si sabemos algo sobre las partes (en este caso, que todas tienen el mismo tamaño), matemáticamente podemos determinar qué parte se refiere a un determinado punto.

Hacer trampa simulación ósea con hueso honesto


Con un algoritmo honesto de simulación ósea, ¿podemos adaptarlo para simular un hueso tramposo? Curiosamente, la respuesta es sí, pero una solución requerirá más espacio.

De la sección anterior, es intuitivamente claro que para simular un tiro de hueso de engaño, es suficiente dividir el intervalo [0,1) en pedazos, y luego determinar qué parte golpeamos. Sin embargo, en el caso general, esto puede ser mucho más complicado de lo que parece. Digamos que tenemos un tetraedro con probabilidades faciales.  frac12 ,  frac13 ,  frac112 y  frac112 (podemos asegurarnos de que esta sea la distribución de probabilidad correcta, porque  frac12+ frac13+ frac112+ frac112= frac612+ frac412+ frac112+ frac112= frac1212 ) Si dividimos el intervalo [0,1) en cuatro partes de estos tamaños, entonces obtenemos lo siguiente:


Desafortunadamente, en este paso estamos estancados. Incluso si supiéramos un número aleatorio en el intervalo [0,1) , entonces no hay trucos matemáticos simples para determinar automáticamente en qué parte cayó este número. No quiero decir que esto sea imposible, como verán, podemos usar muchos trucos excelentes, pero ninguno de ellos tiene la simplicidad matemática del algoritmo honesto de lanzamiento de huesos.

Sin embargo, también podemos adaptar la técnica utilizada para que el hueso honesto funcione en este caso. Tomemos el hueso discutido anteriormente como un ejemplo. La probabilidad de caída de bordes es  frac12 ,  frac13 ,  frac112 y  frac112 . Si reescribimos esto para que todos los miembros tengan un divisor común, obtenemos los valores  frac612 ,  frac412 ,  frac112 y  frac112 . Por lo tanto, podemos percibir esta tarea de la siguiente manera: en lugar de arrojar un hueso tetraédrico con probabilidades ponderadas, ¿por qué no arrojar un hueso honesto de 12 lados, en cuyos bordes hay valores duplicados? Como sabemos cómo simular hueso honesto, esto será análogo a la separación por intervalos [0,1) en pedazos de esta manera:


Luego los asignamos a varios resultados de la siguiente manera:


Ahora será muy simple simular un lanzamiento de hueso: simplemente lanzamos este nuevo hueso honesto, y luego miramos qué cara ha caído y leemos su valor. Este primer paso puede ser realizado por el algoritmo presentado anteriormente, que nos dará un número entero de números en el intervalo 0,1,...,11 . Para unir este entero a una de las caras del hueso tramposo original, almacenaremos una matriz auxiliar de doce elementos que conectan cada uno de estos números con el resultado original. Esto se puede representar gráficamente de la siguiente manera:


Para formalizar esto en forma de algoritmo, describimos tanto la etapa de inicialización (obtención de la tabla) como la etapa de generación (que simula un lanzamiento de hueso al azar). Es importante tener en cuenta estos dos pasos en este y en los algoritmos posteriores, porque el tiempo de preparación debe ser excelente.

En la etapa de inicialización, comenzamos buscando el mínimo común múltiplo de todas las probabilidades dadas para los bordes del hueso (en nuestro ejemplo, el LCL es 12). El NOC es útil aquí porque corresponde al divisor común más pequeño que podemos usar para todas las fracciones y, por lo tanto, al número de caras del nuevo hueso honesto que rodaremos. Habiendo recibido este NOC (lo denotamos con L), debemos determinar cuántas caras del hueso nuevo se distribuirán en cada una de las caras del hueso tramposo original. En nuestro ejemplo, la cara con probabilidad  frac12 obtiene seis lados del hueso nuevo desde  frac12 veces12=6 . Del mismo modo, la fiesta con probabilidad  frac13 tiene 4 caras desde  frac13 veces12=4 . En una forma más generalizada, si L es un LCL de probabilidades, y pi es la probabilidad de una cara i huesos, luego destacamos las caras i hueso Sharpie original L cdotpi facetas de hueso honesto.

Aquí está el pseudocódigo del algoritmo anterior:

Algoritmo: simulando engaño óseo con hueso honesto


  • Inicializacion :
    1. Encuentre el NOC de los denominadores de probabilidad p0,p1,...,pn1 ; denotarlo L
    2. Seleccione una matriz A el tamaño L comparar los resultados de los rollos de hueso honestos con los rollos del hueso original.
    3. Para cada cara i del hueso inicial, realizamos lo siguiente en cualquier orden:
      1. Asignamos de la siguiente manera L cdotpi elementos A valor i .
  • Generacion :
    1. Generamos un tiro de hueso honesto para L hueso de la cara; llama a la cara S .
    2. Volver A[S] .

Este algoritmo puede ser simple, pero ¿qué tan eficiente es? La generación de rollos de hueso es bastante rápida: cada rollo de hueso requiere O(1) tiempo de ejecución para generar una tirada de dados aleatoria usando el algoritmo anterior, y más O(1) Horas de trabajo para buscar en la mesa. Esto nos da el tiempo de trabajo total. O(1) .

Sin embargo, el paso de inicialización puede ser extremadamente costoso. Para que este algoritmo funcione, necesitamos asignar espacio para una matriz del tamaño del NLC de los denominadores de todas las fracciones de entrada. En nuestro ejemplo (  frac12 ,  frac13 ,  frac112 ,  frac112 ), es 12, para otros valores de entrada los valores pueden ser patológicamente incorrectos. Por ejemplo, veamos fracciones  frac9999991,000,000 y  frac11000000 . ¡El NOC de los denominadores es igual a un millón, por lo que debería haber un millón de elementos en nuestra tabla!

Desafortunadamente, las cosas podrían ser aún peores. En el ejemplo anterior, podemos al menos "esperar" que el algoritmo ocupe mucha memoria, ya que ambos denominadores de fracciones son iguales a un millón. Sin embargo, podemos tener muchas probabilidades para las cuales el NOC es significativamente mayor que cada denominador individual. Por ejemplo, veamos las probabilidades  frac115 ,  frac110 ,  frac56 . Aquí el NOC de los denominadores es 30, que es más que cualquiera de los denominadores. El diseño funciona aquí porque 15=3 por5 , 10=2 por5 y 6=2 por3 ; en otras palabras, cada denominador es un producto de dos primos seleccionados de un conjunto de tres valores. Por lo tanto, su NOC es el producto de todos estos números primos, ya que cada denominador debe ser un divisor del NOC. Si generalizamos esta construcción y consideramos cualquier conjunto de k primos y tomar una fracción para cada uno de los productos por pares de estos primos, entonces el NOC será mucho más que cada denominador individual. De hecho, uno de los mejores límites superiores que podemos obtener para el NOC será O( prodni=0di) donde di Es el denominador i esa probabilidad Esto no permite el uso de dicho algoritmo en condiciones reales, cuando las probabilidades son desconocidas de antemano, ya que la memoria requerida para almacenar la tabla de tamaños O( prodni=0di) , Puede resultar fácilmente más del volumen que cabe en la RAM.

En otras palabras, en muchos casos, este algoritmo se comporta bien. Si todas las probabilidades son iguales, entonces todas las probabilidades obtenidas en la entrada son iguales  frac1n para algunos n . Entonces los denominadores NOC son iguales n , es decir, como resultado, el hueso honesto arrojado tendrá n caras, y cada faceta del hueso original corresponderá a una faceta del hueso honesto. Por lo tanto, el tiempo de inicialización es O(n) . Esto se puede representar gráficamente de la siguiente manera:


Esto nos da la siguiente información sobre el algoritmo:

AlgoritmoTiempo de inicializaciónTiempo de generaciónMemoria ocupada
Lo mejorLo peorLo mejorLo peorLo mejorLo peor
Honestidad hueso hueso más afilado Theta(n)O( prodni=0di) Theta(1) Theta(n)O( prodni=0di)

Otro detalle importante sobre este algoritmo: supone que recibiremos probabilidades convenientes en forma de fracciones con buenos denominadores. Si las probabilidades se especifican como IEEE-754 doble, es probable que este enfoque sea desastroso debido a pequeños errores de redondeo; ¡Imagine que tenemos las probabilidades 0.25 y 0.250000000001! Por lo tanto, este enfoque es probablemente mejor no usar, excepto en casos especiales cuando las probabilidades se comportan bien y se especifican en un formato correspondiente a operaciones con números racionales.

Simulación de monedas asimétricas


Nuestra explicación de una simple primitiva aleatoria (hueso honesto) condujo a un algoritmo de simulación de engaño simple pero potencialmente terriblemente ineficaz. Quizás el estudio de otras primitivas aleatorias simples arroje algo de luz sobre otros enfoques para resolver este problema.

Una tarea simple pero sorprendentemente útil es simular una moneda asimétrica utilizando un generador de números aleatorios. Si tenemos una moneda con la probabilidad de un águila pcabezas , entonces, ¿cómo podemos simular el lanzamiento de una moneda tan asimétrica?

Anteriormente, desarrollamos un enfoque intuitivo: partición de intervalos [0,1) en una secuencia de tales regiones que al elegir un valor aleatorio en el intervalo, aparece en alguna región con una probabilidad igual al tamaño de la región. Para simular una moneda asimétrica utilizando un valor aleatorio distribuido uniformemente en el intervalo [0,1) debemos romper el intervalo [0,1) como sigue:


Y luego generar un valor aleatorio distribuido uniformemente en el intervalo [0,1) para ver en qué área está. Afortunadamente, solo tenemos un punto de división, por lo que es muy fácil determinar en qué área se encuentra el punto; si el valor es menor pcabezas , entonces el águila cayó sobre la moneda, de lo contrario - colas. Pseudocódigo:

Algoritmo: simula una moneda asimétrica


  1. Generar un valor aleatorio distribuido uniformemente en el intervalo [0,1) .
  2. Si x<pcabezas , devuelve el "águila".
  3. Si x g e p c a b e z a s  , volver colas.

Como podemos generar un valor aleatorio distribuido uniformemente en el intervalo [ 0 , 1 ) a tiempo O ( 1 ) , y también podemos comparar números reales para O ( 1 ) , entonces este algoritmo se ejecuta a tiempo O ( 1 ) .

Simulando huesos honestos usando monedas asimétricas


De la discusión anterior, sabemos que podemos simular un hueso de engaño usando hueso honesto, si suponemos que estamos listos para gastar espacio de memoria adicional. Dado que podemos percibir una moneda asimétrica como un hueso engañoso de doble cara, esto significa que podemos simular una moneda asimétrica con la ayuda de un hueso honesto. Es interesante que también se pueda hacer lo contrario: simular un hueso honesto con una moneda asimétrica.El diseño es simple, elegante y se puede generalizar fácilmente para simular un hueso tramposo usando una variedad de monedas asimétricas.

El diseño para simular una moneda asimétrica divide el intervalo[ 0 , 1 ) en dos áreas: el área de “águilas” y el área de “colas” según la probabilidad de que las águilas caigan sobre los huesos. Ya hemos visto un truco similar usado para simular honestidadhueso n facetado: intervalo[ 0 , 1 ) se dividió enn áreas iguales. Por ejemplo, al lanzar un hueso tetraédrico, obtuvimos la siguiente separación:


Ahora supongamos que estamos interesados ​​en simular un rollo de este hueso honesto usando un conjunto de monedas asimétricas. Una solución es la siguiente: imagine que recorremos estas áreas de izquierda a derecha, cada vez preguntando si queremos detenernos en el área actual, o si seguiremos adelante. Por ejemplo, supongamos que queremos seleccionar aleatoriamente una de estas áreas. Comenzando desde el área más a la izquierda, lanzaremos una moneda asimétrica, que nos dice si debemos detenernos en esta área o continuar. Como necesitamos elegir entre todas estas áreas de manera uniforme con probabilidad14 , entonces podemos hacer esto lanzando una moneda asimétrica, las águilas en las que caen con probabilidad14 4 .Si cae un águila, nos detenemos en el área actual. De lo contrario, pasamos a la siguiente área.

Si las monedas caen cruz arriba, entonces nos encontramos en la segunda área y nuevamente preguntamos si debemos seleccionar esta área nuevamente o continuar moviéndonos. Podrías pensar que para esto tenemos que lanzar otra moneda con la probabilidad de un águila14 , pero en realidad esto no es cierto! Para ver la falla en este razonamiento, debemos llegar a una situación extrema: si en cada área arrojamos una moneda sobre la cual el águila cae con probabilidad14 , es decir, hay una pequeña posibilidad de que en cada área la moneda se caiga, es decir, tendremos que abandonar todas las áreas. Cuando nos movemos a través de regiones, de alguna manera debemos continuar aumentando la probabilidad de que un águila caiga sobre una moneda. En una situación extrema, si nos encontramos en la última área, entonces la moneda debe tener un águila con probabilidad1 , porque si rechazamos todas las áreas anteriores, entonces la decisión correcta sería detenernos en la última área.

Para determinar la probabilidad de que nuestra moneda asimétrica arroje un águila después de saltar la primera área, debemos notar que después de saltar la primera área solo quedan tres. A medida que rodamos un hueso honesto, necesitamos que cada una de estas tres áreas se seleccione con probabilidad13 3 . Por lo tanto, intuitivamente parece que deberíamos tener un segundo hueso en el que el águila cae con probabilidad 13 3 . Usando un razonamiento similar, puede entenderse que cuando aparece una cola en la segunda región del enrejado en la tercera región, el águila debe dejar caer la moneda con probabilidad 12 , y en la última área - con probabilidad1 .

Esta comprensión intuitiva nos lleva al siguiente algoritmo. Tenga en cuenta que no discutimos la corrección o falacia de este algoritmo; pronto lo haremos.

Algoritmo: simulando huesos honestos usando monedas asimétricas


  1. Para i = 0 an - 1 :
    1. Lanza una moneda asimétrica con la probabilidad de un águila 1n - i .
    2. Si el águila cae, entonces regresa yo .

Este algoritmo es simple y, en el peor de los casos, se ejecuta a tiempo. O ( n ) .Pero, ¿cómo verificamos si es correcto? Para averiguarlo, necesitamos el siguiente teorema:

Teorema: el algoritmo anterior devuelve el ladoyo con probabilidad1n para cualquier seleccionadoyo .

Prueba: considere cualquier constanten 0 . Usando una inducción fuerte, demostramos que cada uno de n caras tiene una probabilidad de elección1n .

Para nuestro ejemplo, mostramos que la cara 0 dados tiene una probabilidad de elección1n . Pero esto se deduce directamente del algoritmo en sí mismo: elegimos la cara 0 si está en una moneda asimétrica con la probabilidad de un águila 1n , 1n .

0,1,2,...,k1 , 1n k . k , k , 1nk . k 1n , , k se da comokn . Esto significa que la probabilidad de que el algoritmo no seleccione uno de los primeros k caras es igual1 - kn =nn -kn =n-kn . Es decir, la probabilidad de elegir una cara. k se da comon - kn 1n - k =1n , que se debe mostrar. Por lo tanto, cada cara del hueso se selecciona de manera uniforme y aleatoria.

Por supuesto, el algoritmo es bastante ineficiente: utilizando la primera técnica, podemos simular una tirada de dados honestos a tiempo O ( 1 ) ! Pero este algoritmo puede usarse como un trampolín para un algoritmo suficientemente efectivo para simular un hueso tramposo usando monedas asimétricas.

Simulación de hueso Shuler utilizando monedas asimétricas.


El algoritmo presentado anteriormente es interesante porque nos da un marco simple para simular un hueso usando un conjunto de monedas. Comenzamos lanzando una moneda para determinar si seleccionamos la primera faceta del hueso o pasamos al resto. En este proceso, necesitamos manejar cuidadosamente la escala de las probabilidades restantes.

Veamos cómo puedes usar esta técnica para simular un tiro de hueso que hace trampa. Usamos nuestro ejemplo con probabilidades12 , 13 3 , 112 , 112 . Él, si no lo recuerdas, divide el intervalo [ 0 , 1 ) como sigue:


Ahora pensemos en cómo simular un hueso tramposo usando monedas asimétricas. Podemos comenzar lanzando una moneda con la probabilidad de un águila12 para determinar si debemos devolver la cara 0. Si un águila cae sobre esta moneda, ¡está bien! Ya hemos terminado De lo contrario, debemos lanzar otra moneda para decidir si seleccionamos la siguiente faceta. Como antes, a pesar de que la siguiente faceta tiene una probabilidad de elección13 , no queremos lanzar una moneda sobre la cual el águila cae con probabilidad13 , porque la mitad de la "masa" de probabilidades se descartó cuando no seleccionamos una línea con12 . De hecho, dado que la mitad de la masa de probabilidades ha desaparecido, si re-normalizamos las probabilidades restantes, obtendremos probabilidades actualizadas: 23 3 , 16 6 , 16 6 . Por lo tanto, la segunda moneda debe ser lanzada con probabilidad 23 3 . Si esta moneda también es cruz, entonces tenemos que elegir entre dos lados 112 . Ya que en esta etapa nos libraremos de 5 56 masas de probabilidades, entonces podemos normalizar nuevamente las probabilidades de las partes112 para que todos tengan una oportunidad12 gotas de un águila, es decir, la tercera moneda tendrá una probabilidad12 . La última moneda, si alguna vez llega a ella, debería arrojar al águila con probabilidad 1 ya que esta es el área más reciente.

Para resumir, las probabilidades de las monedas serán las siguientes:

  1. Primer rollo: 12
  2. Segundo rollo: 23 3
  3. Tercer rollo: 12
  4. Cuarto rollo: 1

Puede ser intuitivo de dónde provienen estos números, pero para convertir la selección en un algoritmo, tenemos que crear una construcción formal de la elección de las probabilidades. La idea será la siguiente: en cada etapa recordamos el resto de la masa de probabilidades. Al principio, antes de lanzar la primera moneda, es igual a1 . Después de lanzar la primera moneda 1 - p 0 . Después de lanzar una segunda moneda 1 - p 0 - p 1 . Más generalmente después del lanzamiento k el resto de la masa de probabilidad es1 - k - 1 i = 0 p i . Cada vez que lanzamos una moneda para determinar si seleccionamos o no un área k , como resultado arrojamos una moneda, la probabilidad de que caiga un águila que es igual a la fracción de la probabilidad restante ocupada por la probabilidadp k , que se define comop k1 - k - 1 i = 0 p i . Esto nos da el siguiente algoritmo para engañar a la simulación ósea con un conjunto de monedas asimétricas (demostraremos su corrección y tiempo de ejecución justo debajo):

Algoritmo: hueso de Schuler de monedas asimétricas


  • Inicializacion :
    1. Mantenemos probabilidades p i para uso futuro.
  • Generación :
    conjuntom a s s = 1
    parai = 0 an - 1 :
    1. Lanza una moneda asimétrica con la probabilidad de un águila p im a s s .
    2. Si el águila cae, entonces regresa yo .
    3. De lo contrario, establecemos m a s s = m a s s - p i

Desde un punto de vista intuitivo, esto es lógico, pero ¿es matemáticamente cierto? Afortunadamente, la respuesta es sí gracias a una generalización de la prueba anterior:

Teorema: el algoritmo que se muestra arriba devuelve una carayo con probabilidadp i para cualquier seleccionadoyo .

Prueba: considere cualquier constanten 0 . , n pi .

, 0 p0 . 0 , , p0mass . mass 1 , p01=p0 , 0 p0 , .

, 0,1,...,k1 p0,p1,...,pk1 k . k , k , pkmass . k , , k k1i=0pi . , k 1k1i=0pi . , k , pkmass k , m a s s = 1 - k - 1 i = 0 p i . Esto significa que la probabilidad general de elegir una cara k se da como( 1 - k - 1 i = 0 p i ) p k1 - k - 1 i = 0 p i =pk, según sea necesario.

Ahora evalúemos la complejidad temporal de este algoritmo. Sabemos que el tiempo de inicialización puede serΘ ( 1 ) si mantenemos una copia de superficie de la matriz de probabilidad de entrada, pero puede haberΘ ( n ) para que podamos guardar nuestra propia versión de la matriz (en caso de que la función de llamada quiera cambiarla más adelante). La misma generación de un resultado de lanzamiento de hueso puede requerir en el peor de los casosΘ ( n ) tiros, y solo un tiro en el mejor de los casos.

Sin embargo, después de reflexionar, queda claro que el número de lanzamientos de monedas necesarios está muy influenciado por la distribución entrante. En el mejor de los casos, tendremos una distribución de probabilidad en la que toda la masa de probabilidades se concentra en el primer borde del hueso, y todas las demás probabilidades son cero. En este caso, un lanzamiento de moneda es suficiente para nosotros. En el peor de los casos, toda la masa de probabilidades se concentra en la última faceta del hueso, y en todas las demás caras es igual a cero. En este caso, tenemos que tirarn monedas

Podemos caracterizar clara y matemáticamente el número esperado de lanzamientos de monedas en este algoritmo. Imaginemos una variable aleatoriaX , que indica el número de lanzamientos de monedas para cualquier ejecución de este algoritmo para una distribución específica. Eso esP [ X = 1 ] es la probabilidad de que para completar el algoritmo sea suficiente para lanzar una moneda,P [ X = 2 ] : la probabilidad de que el algoritmo arroje dos monedas, etc. En este caso, el número esperado de lanzamientos de monedas para nuestro algoritmo está determinado por laexpectativa matemática X denotado porE [ X ] . Por definición, obtenemos que

E [ X ] = n i = 1 i P [ X = i ]


Cual es el significado P [ X = i ] ?El algoritmo termina después de seleccionar algún borde del hueso. Si elige una cara0 , luego lanza una moneda. Si elige una cara1 , luego lanzará dos monedas, una para entender que no quiere elegir una cara0 , otro para entender que quiere elegir una cara1 . Si está más generalizado, entonces si el algoritmo selecciona una cara i , entonces él va a lanzari + 1 monedas:i moneda para decidir que no quiere elegir el anteriori - 1 caras, y una para decidir qué selecciona la carayo . Combinado con el hecho de que sabemos sobre elegir una cara yo con probabilidadp i , esto significa que

E[X]=ni=1iP[X=i]=ni=1ipi1=ni=1((i1)pi1+pi1)=ni=1((i1)pi1)+ni=1pi1


Tenga en cuenta que en la última simplificación, el primer término es equivalente  sumn1i=0i cdotpi que es equivalente  mathbbE[p] , el resultado esperado de un lanzamiento de dados! Además, el segundo término es igual a 1 porque esta es la suma de todas las probabilidades. Esto significa que  mathbbE[X]= mathbbE[p]+1 . Es decir, el número esperado de tiradas de monedas es igual a uno más la expectativa matemática de una tirada de dado.

AlgoritmoTiempo de inicializaciónTiempo de generaciónMemoria ocupada
Lo mejorLo peorLo mejorLo peorLo mejorLo peor
Honestidad hueso hueso más afilado Theta(n)O( prodni=0di) Theta(1) Theta(n)O( prodni=0di)
Schuler hueso de monedas asimétricas Theta(n) Theta(1) Theta(n) Theta(n)

Generalizando monedas asimétricas: simulando un hueso tramposo


En el ejemplo que se muestra arriba, pudimos simular efectivamente una moneda asimétrica, ya que solo teníamos que tener en cuenta un punto de división. ¿Cómo podemos generalizar efectivamente esta idea a un hueso tramposo en el que el número de caras puede ser arbitrario?

Como puede ver, una moneda asimétrica es un hueso tramposo, con solo dos caras. Por lo tanto, podemos percibir una moneda asimétrica simplemente como un caso especial de un problema más general que queremos resolver. Al resolver el problema de la moneda asimétrica, dividimos el intervalo [0,1) en dos áreas, una para el águila, la segunda para las colas, y luego para encontrar el área usamos el hecho de que solo hay un punto dividido. Si tenemos un hueso con cara n, habrá más áreas y, por lo tanto, varios puntos de división. Supongamos, por ejemplo, que tenemos un hueso de siete lados con probabilidades  frac14 ,  frac15 ,  frac18 ,  frac18 ,  frac110 ,  frac110 ,  frac110 . Si queremos dividir el intervalo [0,1) en siete partes, luego lo hacemos de la siguiente manera:


Observe dónde se encuentran estas áreas. La primera área comienza con 0 y termina  frac14 . La segunda área comienza con  frac14 y termina en  frac14+ frac15= frac920 . Más generalmente, si las probabilidades son iguales p0,p1,...,pn1 , entonces las áreas serán intervalos [0,p0) , [p0,p0+p1) , [p0+p1,p0+p1+p2) etc. Esa es el area i limitado por intervalo

[ sumi1j=0pj, sumij=0pj)


Tenga en cuenta que la diferencia entre estos dos valores es pi , es decir, el área total de la región es pi según sea necesario

Ahora sabemos dónde están las áreas. Si queremos elegir un valor aleatorio distribuido uniformemente x en el rango [0,1) , entonces, ¿cómo determinamos en qué intervalo cae? Si utilizamos un algoritmo de monedas asimétricas como punto de partida, la idea será la siguiente: comenzando desde el punto final de la primera región, avanza constantemente por todas las áreas hasta encontrar un punto final cuyo valor sea mayor que el valor x . Si hacemos esto, encontraremos la primera región que contiene el punto x y, por lo tanto, nuestro valor. Por ejemplo, si elegimos un valor aleatorio x= frac2740 , luego realice la siguiente búsqueda:


De lo cual podemos concluir que la faceta 3 cayó en dados con indexación desde cero.

Tal algoritmo de escaneo lineal nos dará un algoritmo de tiempo O(n) para encontrar el borde expulsado del hueso. Sin embargo, podemos mejorar significativamente su tiempo de ejecución usando la siguiente observación: una serie de puntos finales de regiones forma una secuencia creciente (ya que siempre agregamos más y más probabilidades, ninguna de las cuales puede ser menor que cero). Por lo tanto, queremos responder la siguiente pregunta: teniendo una secuencia creciente de valores y algún punto de control, necesitamos encontrar el primer valor en el intervalo estrictamente mayor que el punto de control. ¡Este es el momento perfecto para usar la búsqueda binaria ! Por ejemplo, aquí hay una búsqueda binaria en la matriz de arriba para encontrar el área a la que pertenece x= frac3940 :


Esto nos da un algoritmo con el tiempo.  Theta( logn) para unir un valor aleatorio distribuido uniformemente en el intervalo [0,1) al borde de un hueso abandonado. Además, el tiempo de procesamiento previo es suficiente para construir la tabla de puntos finales  Theta(n) ; simplemente calculamos sumas parciales de probabilidades a medida que avanzamos.

Este algoritmo a veces se denomina algoritmo de selección de la rueda de la ruleta porque selecciona un área aleatoria utilizando una técnica similar a la rueda de la ruleta: lanzar una bola en un intervalo y observar dónde se detiene. En pseudocódigo, el algoritmo se ve así:

Algoritmo: selección de rueda de ruleta


  • Inicializacion :
    1. Seleccione una matriz A el tamaño n
    2. Establecemos A[0]=p0 .
    3. Para cada probabilidad i de 1 antes n1 :
      1. Establecemos A[i]=A[i1]+pi

  • Generacion :
    1. Generar un valor aleatorio distribuido uniformemente x en el rango [0,1)
    2. Usando una búsqueda binaria, encontramos el índice i elemento más pequeño A que es menos x .
    3. Volver i .

La comparación entre este algoritmo y el dado anteriormente parece bastante impresionante:

AlgoritmoTiempo de inicializaciónTiempo de generaciónMemoria ocupada
Lo mejorLo peorLo mejorLo peorLo mejorLo peor
Honestidad hueso hueso más afilado Theta(n)O( prodni=0di) Theta(1) Theta(n)O( prodni=0di)
Schuler hueso de monedas asimétricas Theta(n) Theta(1) Theta(n) Theta(n)
Selección de rueda de ruleta Theta(n) Theta( logn) Theta(n)

Obviamente, ahora tenemos un algoritmo mucho mejor que el original. La discreción de probabilidad solo al principio parecía prometedora, pero este nuevo enfoque, basado en el valor continuo y la búsqueda binaria, se ve mucho mejor. Sin embargo, todavía es posible mejorar estos indicadores con el uso inteligente de un conjunto de técnicas híbridas, que discutiremos a continuación.

Un detalle interesante de este algoritmo es que, aunque el uso de la búsqueda binaria garantiza el peor momento posible para generar números aleatorios O( logn) , tampoco permite una búsqueda más rápida; es decir, el tiempo de generación también será igual  Omega( logn) . ¿Se puede mejorar? Resulta que puedes.

Supongamos que pasamos de una búsqueda binaria en una lista de probabilidades acumulativas a usar un árbol de búsqueda binaria . Por ejemplo, teniendo el conjunto de probabilidades dado anteriormente, podemos construir el siguiente árbol de búsqueda binario para su distribución acumulativa:


Ahora, si queremos simular un rollo de hueso, podemos generar un número distribuido uniformemente en el intervalo [0,1) y luego mira en qué intervalo se encuentra en este árbol de búsqueda binario (BST). Como se trata de un árbol de búsqueda binario equilibrado, el mejor tiempo de búsqueda es O(1) y lo peor O( logn) .

Sin embargo, suponiendo que sepamos más sobre la distribución de probabilidad, podemos hacerlo mucho mejor. Por ejemplo, supongamos que nuestras probabilidades son iguales  frac99100 ,  frac1600 ,  frac1600 ,  frac1600 ,  frac1600 ,  frac1600 ,  frac1600 . Es decir, la distribución de probabilidad es extremadamente sesgada, y casi toda la masa de probabilidades se concentra en una cara. Podemos construir un BST equilibrado para estas probabilidades:


Aunque este árbol de búsqueda binario está perfectamente equilibrado, no es muy adecuado para nuestras tareas. Como sabemos que en 99 de cada 100 casos, el valor aleatorio estará en el rango [0, frac99100) , entonces no tiene sentido almacenar el nodo para este intervalo donde está ubicado ahora. De hecho, esto significará que casi todo el tiempo haremos dos comparaciones innecesarias con las áreas azul y amarilla. Dado que con una probabilidad muy alta deberíamos ser los primeros en verificar el intervalo más grande, sería lógico desequilibrar el árbol para hacer que el caso promedio sea mucho mejor debido a los restantes. Esto se muestra aquí:


Ahora probablemente completaremos la búsqueda al encontrar inmediatamente el área deseada después del primer intento. En el caso muy improbable de que el área deseada esté en el resto ( frac99100,1] bajamos con calma hasta el final del árbol, que en realidad está bien equilibrado.

De forma generalizada, queremos resolver el siguiente problema:

Dado un conjunto dado de probabilidades, encuentre un árbol de búsqueda binario para estas probabilidades que minimice el tiempo de búsqueda esperado.

Afortunadamente, este problema está muy bien estudiado y se llama el problema óptimo del árbol de búsqueda binaria . Hay muchos algoritmos para resolver este problema; se sabe que la solución exacta se puede encontrar a tiempo O(n2) usando programación dinámica , y que existen buenos algoritmos de tiempo lineal que pueden encontrar soluciones aproximadas. Además, para obtener un factor constante de la solución óptima, puede usar la estructura de datos del árbol de despliegue (árbol expansivo) (árbol de búsqueda binaria autobalanceado).

Es interesante que el mejor caso para el comportamiento de tales árboles de búsqueda binarios optimizados ocurre cuando las distribuciones de probabilidad están extremadamente sesgadas, porque simplemente podemos mover los nodos que contienen la gran mayoría de la masa de probabilidad a la raíz del árbol, y el peor de los casos es cuando la distribución está equilibrada, porque entonces el árbol debe ser ancho y poco profundo ¡Esto es lo opuesto al comportamiento del algoritmo anterior, en el que se utilizó uno honesto para simular un hueso tramposo!

En el mejor de los casos, tenemos un hueso tramposo en el que una cara siempre se cae (es decir, tiene una probabilidad de 1, y todas las demás caras tienen una probabilidad de 0). Esta es una exageración extrema de nuestro ejemplo anterior, pero en este caso, la búsqueda siempre terminará después del primer intento. En el peor de los casos, todas las probabilidades son iguales y obtenemos una búsqueda BST estándar. Llegamos a lo siguiente:

AlgoritmoTiempo de inicializaciónTiempo de generaciónMemoria ocupada
Lo mejorLo peorLo mejorLo peorLo mejorLo peor
Honestidad hueso hueso más afilado Theta(n)O( prodni=0di) Theta(1) Theta(n)O( prodni=0di)
Schuler hueso de monedas asimétricas Theta(n) Theta(1) Theta(n) Theta(n)
Selección de rueda de ruleta Theta(n) Theta( logn) Theta(n)
Selección óptima de la rueda de ruletaO(n2) Theta(1)O( logn) Theta(n)

Lanzamiento de dardos


Hasta ahora hemos estado considerando dos primitivas que nos ayudaron a construir algoritmos para simular un hueso tramposo: hueso honesto y moneda asimétrica. Usando solo hueso honesto, llegamos a un algoritmo (por desgracia, poco práctico) para hacer trampa de hueso, y comenzando con monedas asimétricas, pudimos inventar un algoritmo rápido para hacer trampa de hueso. ¿Se pueden combinar estos dos enfoques para crear un algoritmo basado en huesos honestos y monedas asimétricas? Resulta que sí, y de hecho el algoritmo resultante es mejor que ambos enfoques.

Hasta este momento, visualizamos el intervalo [0,1) y probabilidades de caras óseas como un intervalo unidimensional. Ambos algoritmos seleccionan algún punto en el intervalo [0,1) y colóquelo en un segmento de línea recta, cuya longitud corresponde a algún tipo de probabilidad. Cuanto más largos sean los segmentos que creamos, mayor será la probabilidad de elegir este segmento. Pero, ¿qué pasa si intentas pensar no en una sino en dos dimensiones? ¿Qué pasa si tomamos probabilidad pi no la longitud de un segmento de línea recta, sino el área de un rectángulo?

Comencemos volviendo a nuestro ejemplo anterior con probabilidades  frac12 ,  frac13 ,  frac112 ,  frac112 . Representamos estas probabilidades en forma de rectángulos con un ancho w (con algo arbitrario w>0 ) y altura pi (por lo tanto, el área del rectángulo será igual a w cdotpi ):


Tenga en cuenta que el área total de estos rectángulos es w desde la zona

 sumn1i=0wpi=w sumn1i=0pi=w


Ahora supongamos que dibujamos un rectángulo delimitador alrededor de estos rectángulos cuyo ancho es 4w (ya que hay cuatro cuadrángulos), y la altura es  frac12 (ya que el rectángulo más alto tiene una altura  frac12 ):


Podemos imaginar que este rectángulo está dividido en cinco áreas: cuatro áreas corresponden a diferentes probabilidades y un área indica espacio no utilizado. Tomando este descanso, podemos pensar en el algoritmo de simulación de lanzamiento aleatorio de dados como un juego de dardos. Supongamos que lanzamos un dardo (perfectamente distribuido uniformemente) a este objetivo. Si cae en el espacio no utilizado, sacamos el dardo y lo volvemos a lanzar, repitiendo el proceso hasta llegar a uno de los rectángulos. Como cuanto mayor es la probabilidad, cuanto mayor es el rectángulo, mayor es la probabilidad de lanzar el borde del hueso, mayor es la probabilidad de caer en su rectángulo. De hecho, si establecemos la condición de que ya hemos caído en algún tipo de rectángulo, obtenemos lo siguiente:

 mathbbP[ mboxgolpearrectánguloparaelladoi| mboxgolpearalgúnrectángulo]= frac mboxáreadelrectánguloparai mboxáreatotaldelrectángulo= fracwpiw=pi


En otras palabras, cuando finalmente caemos en algún tipo de rectángulo con nuestro dardo distribuido uniformemente, seleccionamos el rectángulo de la cara i hueso tramposo con probabilidad pi , es decir, con la probabilidad que necesitamos! Es decir, si podemos encontrar alguna forma efectiva de simular lanzar dardos aleatorios en este rectángulo, entonces tendremos una forma efectiva de simular lanzar dados aleatorios.

Una forma de simular lanzamientos de dardos en este rectángulo es seleccionar dos valores distribuidos uniformemente en el intervalo [0,1) escalarlos al ancho y alto apropiados, seguido de verificar el área debajo del dardo. Sin embargo, esto causa el mismo problema que tuvimos cuando intentamos determinar la región unidimensional en la que se encuentra el valor aleatorio. Sin embargo, hay una serie de observaciones verdaderamente maravillosas, gracias a las cuales determinar el lugar del impacto puede ser una tarea simple, si no trivial.

Primera observación: mostramos que el ancho de estos rectángulos se puede elegir arbitrariamente, porque todos tienen el mismo ancho. Las alturas, por supuesto, dependen de las probabilidades de las caras de los huesos. Sin embargo, si escalamos uniformemente todas las alturas por algún número real positivo h , entonces las áreas relativas de todos los rectángulos serán las mismas. De hecho, para cualquier número real positivo h área total de todos los rectángulos después de escalar sus alturas en h calculado como

 sumn1i=0whpi=wh sumn1i=0pi=wh


Ahora consideraremos la probabilidad de elegir cualquier rectángulo individual, limitándonos a la condición de que definitivamente golpeemos algún tipo de rectángulo. Usando los mismos cálculos, obtenemos lo siguiente:

 mathbbP[ mboxgolpearrectánguloparaelladoi| mboxgolpearalgúnrectángulo]= frac mboxáreadelrectánguloparai mboxáreatotaldelrectángulo= fracwhpiwh=pi


Es decir, de hecho, la probabilidad de elegir un solo rectángulo no cambia si los escalamos de manera lineal y uniforme.

Dado que podemos elegir cualquier factor de escala adecuado, ¿por qué no escalamos estos rectángulos para que la altura del cuadro delimitador sea siempre 1? Dado que la altura del cuadro delimitador está determinada por el valor máximo pi probabilidades de entrada, entonces podemos comenzar escalando cada uno de los rectángulos por un factor  frac1pmax donde pmax Es la probabilidad máxima de todas las probabilidades de entrada. Gracias a esto, obtenemos la altura del rectángulo 1. De manera similar, dado que podemos elegir cualquier ancho arbitrario para los rectángulos, tomemos el ancho 1. Esto significa que para n las probabilidades del ancho total del cuadro delimitador son n , y la altura total es 1. Esto se muestra en la figura:


Ahora estamos listos para pensar en cómo podemos lanzar un dardo aleatorio en un rectángulo y determinar en qué cayó. Lo más importante es que podemos dividir el rectángulo para que no consista en varios rectángulos más pequeños y un espacio vacío de una forma extraña. En cambio, el área se corta en un conjunto de 2n rectángulos, dos en cada uno de n probabilidades de entrada Esto se muestra aquí:


Observe cómo se forma este rectángulo. Para cada cara del hueso tramposo, tenemos una columna con un ancho de 1 y una altura de 1, dividida en dos espacios: un "sí" de medio espacio que corresponde a un rectángulo de este tamaño y un "no" de medio espacio que corresponde a la parte restante de la columna.

Ahora pensemos en cómo podemos lanzar un dardo. Un dardo perfectamente uniforme lanzado en este rectángulo tendrá componentes x y y . Aquí componente x que debe estar en el intervalo [0,1) , corresponde a qué columna golpea el dardo. Componente y que debe estar en el intervalo [0,1) , corresponde a lo alto que estamos en la columna. Selección de componentes x afecta qué cara del hueso tramposo estamos considerando, y la elección del componente y corresponde a si hemos elegido esta faceta o no. Pero espera, ¡ya conocemos estas dos ideas! Selección coordinada x correspondiente a la columna, similar a tirar un hueso honesto para decidir la elección de la columna. Selección coordinada y corresponde al lanzamiento de una moneda asimétrica para determinar si seleccionar una cara o lanzar nuevamente. Esta observación es tan importante que la hacemos absolutamente comprensible:

La elección de un punto aleatorio en este intervalo es similar a tirar un hueso honesto y lanzar una moneda asimétrica.

De hecho, este resultado puede ser percibido como una oportunidad mucho más poderosa. Para simular un hueso tramposo, construimos un conjunto de monedas asimétricas, una para cada cara del hueso, y luego hacemos rodar un hueso honesto para determinar qué moneda lanzar. , , , , .

. -, — «» pipmax , «» pmaxpipmax . , 1. -, 1 , . , : - , , ( , ). . , , . .

: /


  • :
    1. pi ; pmax .
    2. Coins n , «» .
    3. i 0 antes n1 :
      1. Coins[i]=pipmax

  • :
    1. :
      1. n- i [0,n) .
      2. , Coins[i] .
      3. , i .

. O(n) , O(n) Coins , O(n) . , O(1) . ? , , - . , . , ( 1n ), . , , , , - , . , i pipmax , -

n1i=0(1npipmax)=1nn1i=0pipmax=1npmaxn1i=0pi=1npmax


- , , , , , npmax . ? pmax . pmax 1 ( ). n , n . , , , , . , pmax 1n , , . Si pmax=1n , 1. . Si pmax=1n , ( 1n ), 1, , 1. , , .

, pmax , , , . , , n , , 1. , , «» 1pmax , 1, 1pmax . , «» 1npmax . , , «», pmax . , , .

:

Algoritmo
Θ(n)O(ni=0di)Θ(1)Θ(n)O(ni=0di)
Θ(n)Θ(1)Θ(n)Θ(n)
Θ(n)Θ(logn)Θ(n)
O(n2)Θ(1)O(logn)Θ(n)
/Θ(n)Θ(1)Θ(n) ()Θ(n)

, . . ?

Alias-


, . , . , , «» , . , , , . - , , - , .

, , , . . 12 , 13 , 112 , 112 . , 14 . , 14 , 12 ? , . 1 , :


, 14 1. , , :


1×4 . , :


, , 12 y 13 . ? , 12 112 ? , - , :


, , . , 12 y 13 , . 12 , . , :


, , :


. -, . , ; . , , . -, , , - , , . , . — , . , — , , . , . , , , - ( ).

alias- . -, , . , , . , , , .

, , ? , , . , , , , , . , . , - , , , ( ) , - . (alias) , «» - . - «alias» «alias-».

, , . - ( !), () , , alias- : Prob alias Alias . n . , alias , ( ). , . - - i . Prob[i] . , , i , , Alias[i] . alias :


Alias


, Alias y Prob . , , :

  • (npi)×1 pi ,
  • n
    • , 1 ,
    • , i , i .

, , . , 12 , 13 , 112 , 112 . ( k=n=4 ), 1=44 . , alias, , . , 4, :


, , ( 13 , 13 ) 1. , - . ( ) :


- . , , , 1 ( 2 y 43 ) ; 43 . 43 , ; 23 de 43 , :


, . , 3 , , , . , . , , 1 , (, 23 ) :


, - , 1, . ( 2 ), 13 de 2 :


, . , - , 1 ( 13 ), :


- 1 , . — 53 :


, 1. , :


! .

, :

  • - , 1, , Prob .
  • - , 1, , Alias , .

, ? «», ? , . : , 1 ( 1n , n ) , , , , 1 ( ) 1 ( ). , . , ? , . , , . , .

:

: k h0 , h1 , ..., hk1 , , k1i=0hi=k , k , 1, , , i - i - .

: . , k=1 , 1. 0 - . , 1, , 0 - 0 - .

, - k k+1 1 h0 , h1 , ..., hk , , ki=0hi=k+1 . , hl , , hl1 , - hg (, lg ), , hg1 . , , hl con hl1 ; , hi>1 i 0ik . , k+1=ki=0hi>ki=01=k+1 , . , - l , , hl1 . , hg ( lg ), , hg1 . , hg<1 , ( ) ki=0hi<k+1 . , hl1 y hg1 .

. hl l 1hl en l - hg ( , 01hl1 y hg1 ) . k , k , 1 , k+1 . , l , . , , k en k , . , l , , , . .

, , alias, , . alias.

Alias


, alias-. 1 1, :

: Alias-


  • :
    1. pi en n .
    2. Alias y Prob , n .
    3. For j=1 to n1 :
      1. pl , pl1 .
      2. pg ( lg ), pg1
      3. Prob[l]=pl .
      4. Alias[l]=g .
      5. pl .
      6. pg:=pg(1pl) .

    4. Dejar i , 1.
    5. Prob[i]=1 .

  • :
    1. n - ; i .
    2. , Prob[i] .
    3. , i .
    4. Alias[i] .

, , Θ(1) . . -, Θ(n) n , O(n) . Θ(n) , O(n) , . O(n2) . , :

Algoritmo
Θ(n)O(ni=0di)Θ(1)Θ(n)O(ni=0di)
Θ(n)Θ(1)Θ(n)Θ(n)
Θ(n)Θ(logn)Θ(n)
O(n2)Θ(1)O(logn)Θ(n)
/Θ(n)Θ(1)Θ(n) ()Θ(n)
Alias-O(n2)Θ(1)Θ(n)

alias- , . - (, O(n) ), .

. , O(n) . . pg y pl O(logn) , . pl O(logn) , pg O(logn) . :

: Alias-


  • :
    1. Alias y Prob , n .
    2. T .
    3. npi en T i .
    4. For j=1 to n1 :
      1. T ; pl .
      2. T ; pg .
      3. Prob[l]=pl .
      4. Alias[l]=g .
      5. pg:=pg(1pl) .
      6. pg T .

    5. Dejar i , 1.
    6. Prob[i]=1 .

  • :
    1. n - ; i .
    2. , Prob[i] .
    3. , i .
    4. Alias[i] .

. Alias y Prob - O(n) , BST T Θ(nlogn) . Θ(n) , O(logn) . O(nlogn) :

Algoritmo
Θ(n)O(ni=0di)Θ(1)Θ(n)O(ni=0di)
Θ(n)Θ(1)Θ(n)Θ(n)
Θ(n)Θ(logn)Θ(n)
O(n2)Θ(1)O(logn)Θ(n)
/Θ(n)Θ(1)Θ(n) ()Θ(n)
Alias-O(n2)Θ(1)Θ(n)
Alias-O(nlogn)Θ(1)Θ(n)

, . , , , alias-. «A Linear Algorithm For Generating Random Numbers With a Given Distribution» , alias-.

: 1, 1. . «» , «» «». :

  • «» 1.
  • «» 1.
  • .

, , , . , :

: () Alias-


: . .


  • :
    1. Alias y Prob , n .
    2. , Small y Large .
    3. n .
    4. pi :
      1. Si pi<1 , i Small .
      2. ( pi1 ) i Large .

    5. Small :
      1. Small ; l .
      2. Large ; g .
      3. Prob[l]=pl .
      4. Alias[l]=g .
      5. pg:=pg(1pl) .
      6. Si pg<1 , g en Small .
      7. ($p_g \ge 1$) g en Large .

    6. Large :
      1. Large ; g .
      2. Prob[g]=1 .


  • :
    1. n - ; i .
    2. , Prob[i] .
    3. , i .
    4. Alias[i] .

(, ) : - Small Large , . . Small Large ( Small , , ). Large 1, k Large k , Large 1, . 1, , , 1.

. , , , . .

, . 14 , 15 , 18 , 18 , 110 , 110 , 110 . , , 18 , 15 , 110 , 14 , 110 , 110 , 18 . :


Small , :


Large ( ) . 7418=1381 , Large :


. Small , Large :


:


, , , . , :


Small , :


Small , , :


, Small , .


alias .


, . , , IEEE-754 double, . , , :

  1. , Small Large , . , , n , , 1n , 1 ( Small , Large )
  2. , , . , , Large , Small .

, Small Large . , , Small , Large .

, . , , , Large . -, , 1 , , 1 . , . :

: Alias-


  • :
    1. Alias y Prob , n .
    2. , Small y Large .
    3. n .
    4. pi :
      1. Si pi<1 , i en Small .
      2. ( pi1 ) i en Large .

    5. Small y Large : ( Large )
      1. Small ; l .
      2. Large ; g .
      3. Prob[l]=pl .
      4. Alias[l]=g .
      5. pg:=(pg+pl)1 . ( . )
      6. Si pg<1 , g en Small .
      7. ( pg1 ) g en Large .

    6. Large :
      1. Large ; g .
      2. Prob[g]=1 .

    7. Small : - .
      1. Small ; l .
      2. Prob[l]=1 .


  • :
    1. n - ; i .
    2. , Prob[i] .
    3. , i .
    4. Alias[i] .

, — . Θ(n) , . Θ(1) , , . O(n) , () , . O(n) , Large y Small O(n) . Θ(n) , ( ) :
Algoritmo
Θ(n)O(ni=0di)Θ(1)Θ(n)O(ni=0di)
Θ(n)Θ(1)Θ(n)Θ(n)
Θ(n)Θ(logn)Θ(n)
O(n2)Θ(1)O(logn)Θ(n)
/Θ(n)Θ(1)Θ(n) ()Θ(n)
Alias-O(n2)Θ(1)Θ(n)
Alias-O(nlogn)Θ(1)Θ(n)
Alias-Θ(n)Θ(1)Θ(n)


Wow! ! , . , (alias- ) , - .

alias- , , - , alias- Java , .

, !

Source: https://habr.com/ru/post/es419725/


All Articles