中国 - 喜马拉雅

       Let life be beautiful like summer flowers . . .

高效生成随机数组算法在实际的程序开发中也大量的进行使用。例如在棋牌类的游戏开发中的斗地主、双扣等,在每次游戏开始时,都需要打乱扑克牌的顺序,然后再将打乱以后的扑克牌依次发放给每个游戏玩家。而这种打乱扑克牌顺序的算法通常就被称作洗牌算法。

从程序开发的角度看洗牌算法,实际上生成的就是一组规则的随机数字,例如对于一副扑克牌来说,在程序中如果需要代表一副扑克牌,根据扑克牌的特点(每幅牌包含54张不同的牌)则只需要为每张牌进行编号即可,例如将扑克牌中的每张牌依次对应编号成1-54之间的整数,那么一副牌就是一个包含1-54之间所有整数的数组。而洗牌就是将这样一个规则的数组变成一个随机数组,但是必须包含所有1-54之间的所有整数。

其中一种实现方案为:

  1. 随机产生一个1-n的数x,然后让第x张牌和第1张牌互相调换。
  2. 随机产生一个1-n的数y,然后让第y张牌和第2张牌互相调换。
  3. 随机产生一个1-n的数z,然后让第z张牌和第i张牌互相调换。(i=3,4,5...54)

这种算法的时间复杂度为O(N)。

这种方案乍看好像不错,网上也有很多文章使用这种算法,但是其实这是一种错误的方法,因为这种方案的所有可能性为N^N,而洗好的牌一种有N!种可能,又因为N^N % N! !=0,所以每种结果的概率是不相同的。

那么如何修正这个问题呢?第i次洗牌不是产生一个1-n的随机数,而是产生一个i-n的随机数,这样可能性结果的可能性就是N!了。就有可能概率相等了。证明在《计算机程序设计艺术》上。

示例代码如下:

#include <time.h>
#include <cstdlib>
#include <iostream>

using namespace std;

#define swap(a,b) {int t=(a);(a)=(b);(b)=(t);}

class Poker
{
private:
	static const int poker_size = 54;
	int poker[poker_size];

public:
	Poker()
	{
		Init();
	}
	~Poker(){}

	void Init()
	{
		for(int i = 0; i < poker_size; i++)
		{
			poker[i] = i + 1;
		}
	}

	void Show()
	{
		for(int i = 0; i < poker_size; i++)
		{
			if(i != 0 && i % 10 == 0) printf("\n");
			printf("%5d", poker[i]);
		}
		printf("\n");
	}

	void Shuffle()
	{
		srand((int)time(0));
		for(int i = 0; i < poker_size; i++)
		{
			// 随机下标为[0,poker_size - 1.0]之间的随机数,与下标i数据交换
			//int ranIndex = 0 + 
				//(int)((poker_size - 1.0 - 0 + 1.0) * rand() / (RAND_MAX + 1.0));
			// 改正后,随机下标为[i,poker_size - 1.0]之间的随机数,与下标i数据交换
			int ranIndex = i + 
				(int)((poker_size - 1.0 - i + 1.0) * rand() / (RAND_MAX + 1.0));
			if(i != ranIndex)
			{
				swap(poker[i], poker[ranIndex]);
			}
		}
	}
};

int main()
{
	// 创建并初始化poker
	Poker poker;

	// 洗牌前,扑克按升序排列:
	cout << "Before Shufflling:" << endl;
	poker.Show();

	// 洗牌
	poker.Shuffle();

	// 先牌后,扑克顺序被打乱
	cout << "After shufflling:" << endl;
	poker.Show();

	return 0;
}

一种可能的结果为:

poker

参考自:

云水居

pdw_jsp的专栏

posted on 2012-10-10 16:25  chinaxmly  阅读(1778)  评论(0编辑  收藏  举报