Struttura dei dati per i dadi caricati?


130

Supponiamo che io abbia un dado caricato a lato N in cui ogni lato k ha qualche probabilità p k di salire quando lo lancio. Sono curioso di sapere se esiste un buon algoritmo per la memorizzazione statica di queste informazioni (cioè per un insieme fisso di probabilità) in modo da poter simulare in modo efficiente un tiro casuale del dado.

Attualmente, ho una soluzione O (lg n) per questo problema. L'idea è di memorizzare una tabella della probabilità cumulativa dei primi k lati per tutti i k, per generare un numero reale casuale nell'intervallo [0, 1) ed eseguire una ricerca binaria sopra la tabella per ottenere l'indice più grande il cui cumulativo il valore non è maggiore del valore scelto. Preferisco questa soluzione, ma sembra strano che il runtime non tenga conto delle probabilità. In particolare, nei casi estremi di un lato sempre crescente o di distribuzione uniforme dei valori, è possibile generare il risultato del rollio in O (1) usando un approccio ingenuo, sebbene la mia soluzione continuerà a fare logaritmicamente molti passi.

Qualcuno ha qualche suggerimento su come risolvere questo problema in un modo che è in qualche modo "adattivo" nel suo runtime?

EDIT : sulla base delle risposte a questa domanda, ho scritto un articolo che descrive molti approcci a questo problema , insieme alle loro analisi. Sembra che l'implementazione del metodo alias da parte di Vose dia Θ (n) tempo di preelaborazione e O (1) tempo per tiro di dado, il che è davvero impressionante. Speriamo che questa sia un'utile aggiunta alle informazioni contenute nelle risposte!


2
È ragionevole che esista una soluzione O (1) per ciascun caso specifico .
Tim

Risposte:


117

Stai cercando il metodo alias che fornisce un metodo O (1) per generare una distribuzione di probabilità discreta fissa (supponendo che tu possa accedere alle voci in un array di lunghezza n in tempo costante) con una configurazione O (n) una tantum . Puoi trovarlo documentato nel capitolo 3 (PDF) di "Generazione di variabili casuali non uniformi" di Luc Devroye.

L'idea è di prendere la tua gamma di probabilità pk e produrre tre nuove matrici di elementi n, q k , ak e bk . Ogni q k è una probabilità tra 0 e 1, e ciascuno a k e b k è un numero intero compreso tra 1 e n.

Generiamo numeri casuali tra 1 e n generando due numeri casuali, r e s, tra 0 e 1. Sia i = floor (r * N) +1. Se q i <s quindi restituisce a i else return b i . Il lavoro nel metodo alias è a capire come produrre q k , un k e b k .


Per un algoritmo così utile, il metodo Alias ​​è sorprendentemente poco noto.
mhum,

Per la cronaca: ho pubblicato una piccola libreria C per il campionamento casuale usando il metodo alias apps.jcns.fz-juelich.de/ransampl .
Joachim W

1
un'implementazione specifica del metodo alias può essere più lenta di un metodo con una complessità temporale peggiore come la Ruota della Roulette per un dato ne per un numero scelto di numeri casuali da generare a causa di fattori costanti coinvolti nell'implementazione di algoritmi.
jfs,

4

Utilizzare un albero di ricerca binario bilanciato (o ricerca binaria in un array) e ottenere complessità O (log n). Avere un nodo per ogni risultato di die e avere le chiavi come intervallo che attiverà quel risultato.

function get_result(node, seed):
    if seed < node.interval.start:
        return get_result(node.left_child, seed)
    else if seed < node.interval.end:
        // start <= seed < end
        return node.result
    else:
        return get_result(node.right_child, seed)

La cosa positiva di questa soluzione è che è molto semplice da implementare ma ha ancora una buona complessità.


L'albero binario fatto a mano come sopra è semplice da implementare ma non è garantito bilanciato
yusong

Puoi garantire che sia bilanciato se lo costruisci nell'ordine corretto.
hugomg,

3

Sto pensando di granulare il tuo tavolo.

Invece di avere una tabella con il valore cumulativo per ciascun valore di matrice, è possibile creare un array intero di lunghezza xN, dove x è idealmente un numero elevato per aumentare la precisione della probabilità.

Popola questo array usando l'indice (normalizzato da xN) come valore cumulativo e, in ogni 'slot' dell'array, archivia il tiro di dadi aspirante se questo indice compare.

Forse potrei spiegare più facilmente con un esempio:

Usando tre dadi: P (1) = 0,2, P (2) = 0,5, P (3) = 0,3

Crea un array, in questo caso sceglierò una lunghezza semplice, diciamo 10. (ovvero x = 3.33333)

arr[0] = 1,
arr[1] = 1,
arr[2] = 2,
arr[3] = 2,
arr[4] = 2,
arr[5] = 2,
arr[6] = 2,
arr[7] = 3,
arr[8] = 3,
arr[9] = 3

Quindi per ottenere la probabilità, basta randomizzare un numero compreso tra 0 e 10 e accedere semplicemente a quell'indice.

Questo metodo potrebbe perdere la precisione, ma aumenterà x e la precisione sarà sufficiente.


1
Per la massima precisione è possibile eseguire la ricerca dell'array come primo passo e per intervalli di array che corrispondono a più lati eseguire una ricerca lì.
aaz,

1

Esistono molti modi per generare un numero intero casuale con una distribuzione personalizzata (nota anche come distribuzione discreta ). La scelta dipende da molte cose, tra cui il numero di numeri interi tra cui scegliere, la forma della distribuzione e se la distribuzione cambierà nel tempo.

Uno dei modi più semplici per scegliere un numero intero con una funzione di peso personalizzata f(x)è il metodo di campionamento del rifiuto . Quanto segue presuppone che il valore più alto possibile di fsia max. La complessità temporale per il campionamento del rifiuto è in media costante, ma dipende in gran parte dalla forma della distribuzione e ha il peggior caso di esecuzione per sempre. Per scegliere un numero intero in [1, k] utilizzando il campionamento del rifiuto:

  1. Scegli un numero intero casuale uniforme iin [1,k ].
  2. Con probabilità f(i)/max, ritorna i. Altrimenti, vai al passaggio 1.

Altri algoritmi hanno un tempo di campionamento medio che non dipende molto dalla distribuzione (di solito costante o logaritmica), ma spesso richiedono di precalcolare i pesi in una fase di installazione e di memorizzarli in una struttura di dati. Alcuni di essi sono anche economici in termini di numero di bit casuali che usano in media. Molti di questi algoritmi sono stati introdotti dopo il 2011 e includono:

  • la struttura dei dati succinta di Bringmann – Larsen ("Succinct Sampling from Discrete Distributions", 2012),
  • La ricerca multilivello di Yunpeng Tang ("Uno studio empirico sui metodi di campionamento casuali per cambiare le distribuzioni discrete", 2019), e
  • il rullo per dadi a caricamento rapido (2020).

Altri algoritmi includono il metodo alias (già menzionato nel tuo articolo), l'algoritmo Knuth – Yao, la struttura dei dati MVN e altro. Vedi la mia sezione " Una nota sugli algoritmi di scelta ponderata " per un sondaggio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.