我一直在进行一些休闲的计算机操作。我的小项目是模拟意大利游戏“tomboli”。其中一个关键构建块是以下过程的模拟:
游戏由一名手持编号为1至90的90颗弹珠的袋子的男子控制。他随机地从袋子中抽取弹珠,每次向玩家呼叫弹珠号码。
经过一番思考,我编写了以下代码来实现这个构建块;
// 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];
}
}
我知道在使用随机数进行游戏模拟时,到处都有微妙和陷阱,所以尽管我对我的代码感到相当满意,但我的信心还不到100%。所以我的问题是:
1)我的代码有什么问题吗?
2)[如果对1的答案是否定的] 我是否在无意中使用了标准的洗牌算法?
3)[如果对2的答案是否定的] 我的算法与标准替代方案相比如何?
编辑 感谢所有回答的人。我将接受 Aidan Cully 的答案,因为事实证明我正在重新发现 Fisher-Yates 算法,并且揭示了这一点是关键所在。当然,如果我事先做些研究,我就不会浪费时间和精力了。但另一方面,这是一个有趣的爱好项目。模拟的其余部分很常规,这是最有趣的部分,如果我不自己尝试一下,我就会剥夺自己的乐趣。此外,我试图模拟一个从袋子里取出弹珠的人,直到相当晚的时候我才意识到情况与洗牌卡片完全类似。
另一个有趣的事实是,Ken 发现了一个小缺陷,即经常重复的 rand()%N 模式不是从范围 0..N-1 中选择随机数的好方法。
最后,我的 Fisher-Yates 版本缺乏优雅的技巧,这使得洗牌变得简单易行。因此,我的算法会得到同样随机但是反向的洗牌。
srand(time(0));
。 - OmarOthman