Czy jest coś nie tak z tym algorytmem tasowania?
Robię trochę komputerowego wypoczynku rekreacyjnego. Mój mini-projekt był symulacją włoskiej gry „tomboli”. Kluczowym elementem konstrukcyjnym była symulacja następującego procesu;
Gra jest kontrolowana przez mężczyznę z workiem 90 kulek, ponumerowanych od 1 do 90. Losuje on losowo kulki z worka, za każdym razem wywołując wśród graczy numer marmuru.
Po krótkiej myśli napisałem następujący kod dla tego bloku konstrukcyjnego;
// NBR marbles, numbered 1...NBR are in a bag. Simulate randomly
// pulling them from the bag, one by one, until the bag is empty
void bag( int random_sequence[NBR] )
{
int i;
// Store each marble as it is pulled out
int *store = random_sequence;
// Array of marbles still in the bag
int not_yet_pulled[NBR];
for( i=0; i<NBR; i++ )
not_yet_pulled[i] = i+1; // eg NBR=90; 1,2,3 ... 90
// Loop pulling marbles from the bag, one each time through
for( i=NBR; i>=1; i-- )
{
int x = rand();
int idx = x%i; // eg i=90 idx is random in range 0..89
// eg i=89 idx is random in range 0..88
// ...
// eg i=1 idx is random in range 0..0
// (so we could optimize when i=1 but not worth the bother)
*store++ = not_yet_pulled[idx];
// Replace the marble just drawn (so it cannot be pulled again)
// with the last marble in the bag. So;
// 1) there is now one less marble in the bag
// 2) only marbles not yet pulled are still in the bag
// If we happened to pull the last marble in the *current subarray*, this is
// not required but does no harm.
not_yet_pulled[idx] = not_yet_pulled[i-1];
}
}
Wiem, że w symulacji gry z liczbami losowymi są subtelności i pułapki, więc chociaż jestem całkiem zadowolony z mojego kodu, moje zaufanie jest nieco mniejsze niż 100%. Więc moje pytania są;
1) Czy coś jest nie tak z moim kodem?
2) [jeśli odpowiedź na 1) jest nie] Czy nieświadomie korzystam ze standardowego algorytmu tasowania?
3) [jeśli odpowiedź na 2) brzmi nie] Jak mój algorytm porównuje się do standardowych alternatyw?
EDYTOWAĆ Dziękujemy wszystkim, którzy odpowiedzieli. Zamierzam zaakceptować odpowiedź Aidana Cully'ego, ponieważ okazuje się, że odkrywałem na nowo algorytm Fishera-Yatesa i ujawniający to dociera do sedna sprawy. Oczywiście nie jest zaskoczeniem, że mogłem zaoszczędzić czas i wysiłek, wykonując pewne badania z góry. Ale z drugiej strony był to zabawny projekt hobby. Reszta symulacji była rutynowa, to była najciekawsza część, a ja sam pozbawiłbym się przyjemności, bo sam nie musiałem iść. Dodatkowo próbowałem symulować mężczyznę biorącego kulki z torby i było dość późno w tym kawałku, że zdałem sobie sprawę, że sytuacja była dokładnie analogiczna do tasowania kart.
Kolejny interesujący jest fakt, że istnieje mała wada, zidentyfikowana przez Kena, który podkreśla, że często powtarzany wzór rand ()% N nie jest dobrym sposobem na wybranie losowej liczby z zakresu 0..N-1.
Wreszcie w mojej wersji Fisher-Yates brakuje eleganckiej sztuczki, która pozwala na ładną właściwość tasowania na miejscu. W rezultacie mój algorytm skończyłby się równie losowym, ale odwróconym tasowaniem.