使用模拟在一手 6 张牌中恰好有一张大牌

Exactly one high card in a 6 card hand using simulations

所以我有一个概率问题(出于纯粹的无聊)我决定尝试使用模拟来解决。 问题:在一手 6 张牌中,恰好抽到一张大牌的概率是多少。

现在,这个问题专门针对在我的国家/地区玩的某些游戏,因此出于某种奇怪的原因,有 21 张大牌,但这并不重要。

手动解决这个问题,使用我得到的基本组合:

现在,这是我在 C 中模拟它的方式:

主要功能:

int main(void)
{

    srand(time(0));
    int deck[52];

    int i;
    for(i = 0; i<21; i++) deck[i] = 1;
    for(i = 21; i<52; i++) deck[i] = 0;

    int n;
    printf("# of simulations: ");
    scanf("%d", &n);

    int memo[52];

    int hits = 0;
    for(i = 0; i<n; i++){
      clear_memo(memo);
      hits += simulate(deck, memo);
    }
    printf("Result: %lf\n", (double)hits/n);


}

因此,牌组 是一个包含 52 个数字的数组,其中前 21 个元素的值为 1(高牌),其他 31 个元素的值为 0(低牌)。

每次都会将备忘录发送到模拟功能,以跟踪哪些牌已经被抽出。备忘录也会在每次使用 clear_memo 函数时重置,该函数除了将所有值设置为零外什么都不做。

然后它调用模拟函数并计算命中数。

模拟函数如下:

int simulate(int * deck, int * memo){

  //I draw the first card separetly in order to initialize the had_high variable
  int index = ( rand() % 52 );
  int card = deck[index];
  int had_high = (card == 1);
  memo[index] = 1;

  //printf("%d ", index);

  int i = 1;
  while(i < 6){

    int draw = (rand() % 52);
    //printf("%d ", draw);
    if(memo[draw]) continue;

    index = draw;
    card = deck[index];
    memo[index] = 1;

    if(card){
        if(had_high) { //meaning there are 2 high cards, no hit
          //printf("\n");
          return 0;
        }
        had_high = 1; //if not, then this is the first high card
    }

    i++;
  }
  printf("\n");
  return had_high; //the function would've reached this point even if all the cards had been low
                   //therefore I return had_high instead of just 1

}

模拟功能本身就可以,我单独测试了很多次,好像没有问题。

然而,当我 运行 具有大量模拟(100k 或 1m)的程序时,结果总是大约。 0.175 这不是我用手算得到的。

我有理由相信我的手算是正确的(但如果我也错了请纠正我)。

如果我的手工计算是正确的,那么我模拟此事件的方式一定有问题。我的一个想法是它与 rand 函数有关,以及它是如何伪随机的,但我真的不知道,因为很难测试任何适用于随机数。

有什么想法吗?

谢谢。

编辑: 根据 klutt 的要求:

void clear_memo(int * memo){
  int i = 0;
  for(;i<52;i++) memo[i] = 0;
}

我的程序给出的结果与你的相同 - 大约 0.175

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    int deck[52];
    int successes = 0;

    srand((unsigned)time(NULL));
    for(int run = 0; run < 100000; run++) {
        for(int n = 0; n<52; n++) {
            deck[n] = n;
        }
        int cards = 52;
        int highs = 0;
        for(int n=0; n<6; n++) {
            int index = rand() % cards;
            if(deck[index ] < 21) {
                highs++;
            }
            deck[index] = deck[--cards];
        }
        if(highs == 1) {
            successes++;
        }
    }
    printf("Probability of drawing exactly one high card = %f\n", successes / 100000.0);
}

但是组合学在两个方面是错误的:

  • 一包中只有 31 "low" 张牌,所以表达式应该是
21   31   30   29   28   27
__ . __ . __ . __ . __ . __   = 0.02921

52   51   50   49   48   47
  • 其次,6次开奖中的任何一次都可以是"high",而不仅仅是第一次,所以 将机会乘以 6。
0.02921 * 6 = 0.1752

你的计算有误

你计算的是第一张牌是高牌而其余牌是低牌的概率。或者至少看起来你正在尝试这样做。你有点偏离。应该是(21/52)*(31/51)*(30/50)*(29/49)*(28/48)*(27/47) = 0.02921...

你应该把它乘以 6,因为高牌可以出现在任何地方。那么你有恰好一个高的概率,即 0.17526

rand() % n 分布不均匀

话虽这么说,但请注意 C 中的 运行dom 生成器不太适合以这种方式使用。根据您使用它的方式,它可能会出现可怕的分布。如果您使用的是 C++,则可以使用:

std::random_device rd;
std::default_random_engine generator(rd());
std::uniform_int_distribution<int> distribution(0,51);
int index = distribution(generator);

在这样的模拟中,这可能会产生巨大的影响。在这种情况下,它的影响很小。我测试了标准 rand() 方法和 C++ 变体以及 运行 模拟 4 次,每次迭代 1000 万次:

使用rand() % 52

Result: 0.175141
Result: 0.175074
Result: 0.175318
Result: 0.175506

使用distribution(generator)

Result: 0.175197
Result: 0.175225
Result: 0.175228
Result: 0.175293

可以看到,偏差变小了。因此,如果准确性很重要,要么考虑切换到 C++ 并使用这些方法,要么找到一种获得良好分布的方法。如果您正在进行模拟以数值计算概率,那么它 很重要。