完全不同的随机数生成

前言

之前研究红黑树,也就是索引相关的操作,写好了对索引数据的增删改查功能。为了测试算法的正确性和稳定性,选择了测试唯一索引,因为集合索引只造成数据的重复,并且向程序插入512M(约5亿)个唯一的数据,生成这么多的数据,如果是递增或者递减的,这种数据很好生成,但要生成随机的数据,并且互不相等,就需要花点时间来研究算法。
我用的是C语言来实现的,所以,相关描述也是针对C语言的

实现方案

1. 用rand函数生成随机数,如果结果缓冲区不存在,放进去

这种方法,在数量比较少的情况下,比如10个数据,100个数据,也许能实现。但在数量比较多的情况下,即使能实现,耗时也大大超过了计划。
rand()生成的随机数,范围是0~65535,一共65536个数,但不是说,调用了rand() 65536次后,0到65535的数一定都会出现一次。因为是随机性的,并且是统计概率,有可能调用1亿次,每个数才能都出现。
另外,由于判断数是否已经存在缓冲区,虽然可以用二分法来实现,但是,如果要生成的数据很多,比如5亿个数据,对这些数据都判断,也要消耗很多时间。
总结,这个方案理论可行,实际不可行。

2. 洗牌法,模拟手工洗牌,把已排好序的唯一数据洗乱

这种方法,可以初始化一个排行序的数组,再用类似洗牌的方法,对这个数组洗乱。
初始化的代码如下。

int iArray[65536];
int iTmp;

//初始化数据
for (iTmp = 0; iTmp < 65536; iTmp++)
{
  iArray[iTmp] = iTmp;
}

手工洗牌的方法很多,无法就是取出一部分数据,放到牌的最前面或者最后面,以达到牌序变乱,洗牌的次数越多,牌序就越乱。
我开始就是这么做的,但是,分配了5个亿的数据,也就是512M个,每个数据32b,也就是4B,总数据量达到2GB,洗一次牌就相当于拷贝了2GB的数据,而内存拷贝数据的速度也就8GB每秒左右,1秒能洗4次的牌。并且,这么大的数据量,不可能只洗1次,起码100万次,这样下来,耗时25万秒,相当于3天时间,这个时间还是太长了。
总结,这个方法,在数据量比较少的情况下,是可以实现的,但数据量比较多,即使能实现,也不符合实际。

3. 抽签法,从排好序的唯一数据中,通过抽签来取出一个数据,放到结果中

这个方法,也是从洗牌法里面想出来的,不过是一种很装X的洗牌,平时手工洗牌就是上面那种,但有些人,就喜欢把牌往天上撒,等牌掉下来,再一张一张捡起来叠好,这样牌序就是乱的。
这种想法,最后演变成抽签法。初始化一个排好序的数组,通过rand()生成随机数,如果数据量超过65536,则生成2个随机数,第一个数乘以65536再加上第二给随机数,对剩下的数据取模,把结果作为下标,从剩下的数据取出来,放到缓冲区,再将末尾或者前面的数据移到被抽出的数据中。
比如当前数据组是iArrayData[],开始位置是iIndex1,数据量一共是iTotal,通过rand()生成的两个数是r1和r2,则抽出的数据的相对下标是 iIndex2=iTotal%(r1*65536+r2),则在数组中的绝对下标是iIndex3=iIndex1+iIndex2,需要把iData1=iArrayData[iIndex3]取出来,放到结果缓冲区的末尾。
大概代码如下

#define DATA_SIZE 512*1024*1024 //定义数据量为512M
int iArrayData[DATA_SIZE];//数据数组为512M个数据
int iArrayRlt[DATA_SIZE]; //结果数组数据
int iRltSize = 0; //结果数组下标
int iTmp;
int iIndex1;
int iIndex2;
int iIndex3;
int r1;
int r2;
int iData1;

//初始化数据
for (iTmp = 0; iTmp < DATA_SIZE; iTmp++)
{
  iArrayData[iTmp] = iTmp;
}

int iTotal = DATA_SIZE;  //剩余的数据量

//从初始化的数组中抽出数据
iIndex1 = 0;
for (iTmp = 0; iTmp < DATA_SIZE; iTmp++)
{
  r1 = rand(); //第一个随机数
  r2 = rand(); //第二个随机数
  iIndex2 = iTotal/(r1*65536+r2);//相对下标
  iIndex3 = iIndex2+iIndex1; //绝对下标
  iArrayRlt[iTmp] = iArrayData[iIndex3];//取出数据,放到结果缓冲区末尾
  //原来第一个数据移到被抽出的数据中
  if (iIndex1 != iIndex3)//如果iIndex2==0,取的就是当前数据的第一个,否则,是其他位置的数据,就要将第一个数据放到其他数据的位置
  {
    iArrayData[iIndex3] = iArrayData[iIndex1];
  }
  //将第一个数据位置加1
  iIndex2++;
  //将剩余数据量减1
  iTotal--;
}
//数据生成完毕

这个方案,主要时间在rand()里面了,其他操作基本不耗时间,实际上,生成5亿个完全不一样的数据,大概花了60秒。

实际的代码如下

unsigned int *piArray;
unsigned int *piResult;
unsigned int iSize;
int iRandomTmp;
unsigned int iMillitm;
unsigned int iLeftSize;
unsigned int iHigh1;
unsigned int iHigh2;
unsigned int iLow1;
unsigned int iLow2;
unsigned int iStartIndex;
unsigned int iEndIndex;
unsigned int iIndex;
unsigned int iFlag;
unsigned int iBase;
void createRandom(int iaSize) 
{
  iSize = iaSize;
	/*初始化*/
	piArray = malloc( sizeof(int) * iSize );
	if (piArray == NULL)
	{
		fprintf( stderr, "get ram[%lu] fail\n", sizeof(int) * iSize );
		return 0;
	}
	for (iRandomTmp = 0; iRandomTmp < iSize; iRandomTmp++)
	{
		*(piArray + iRandomTmp) = iRandomTmp;
	}
	/*初始化*/
	piResult = malloc( sizeof(int) * iSize );
	if (piResult == NULL)
	{
		fprintf( stderr, "get ram[%lu] fail\n", sizeof(int) * iSize );
		return 0;
	}
	memset( piResult, 0x00, sizeof(int) * iSize );
	iLeftSize = iSize;
	iStartIndex = 0;
	iEndIndex = iSize - 1;
	iFlag = 0;
	for (iRandomTmp = 0; iRandomTmp < iSize; iRandomTmp++)
	{
		iMillitm = getMillitm();
		iHigh1 = (((unsigned int )rand()) * iMillitm)& 0x000000FF;        /*取低8位*/
		iHigh2 = (((unsigned int )rand()) * iMillitm) >> 8;               /*取高8位*/
		iLow1 = (((unsigned int )rand()) * iMillitm)& 0x000000FF;         /*取低8位*/
		iLow2 = (((unsigned int )rand()) * iMillitm) >> 8;                /*取高8位*/
		iIndex = ((iHigh1 << 24) + (iHigh2 << 16) + (iLow1 << 8) + iLow2)%iLeftSize;
		*(piResult + iRandomTmp) = *(piArray + iIndex + iStartIndex) + iBase;
		if (iFlag == 0)
		{
			*(piArray + iIndex + iStartIndex) = *(piArray + iEndIndex);
			iEndIndex--;
			iFlag == 1;
		}
		else
		{
			*(piArray + iIndex + iStartIndex) = *(piArray + iStartIndex);
			iStartIndex++;
			iFlag = 0;
		}
		iLeftSize--;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值