从一个链表中等可能取出若干节点

题目是这样的:给定一个单向链表,遍历一遍,从中选出M点,要求每个节点被选中的概率相同。

这主要是一个概率论的问题,既然是单向链表,分两种来讨论

1.如果我们事先就知道链表的长度

那么这个问题就变为一个经典的抽签问题--在不知道前面人抽到的结果情况下,每个人抽中奖的概率是一样的

证明如下:

(1).假设一个票箱里面有N张票,其中M张是奖票,那么第一个人抽中的概率是M/N,抽不中的概率是(N-M)/N

(2).第二个人来抽签,他不知道前面一个人抽中否,如果第一个人抽中,那么奖票少了一张,他中奖的概率为(M-1)/(N-1),前面一个人没有中奖,他中奖的概率为M/(N-1),那么由全概率公式得到他中奖的概率为 M/N * (M-1)/(N-1)+(N-M)/N * M/(N-1),可以算一下结果为 M/N,也就是说第二个和第一个中奖的概率是一样的。

然后以此类推,这是一个古典概率类型。


2.不知道链表长度。

    这个在很多场合都是有用的,比如一个网站每天的访问量是不确定的,要从每天访问的URL里面尽可能公平的抽出一些来进行分析。

    如果不知道总长度,我们在任意时刻可以知道已经访问了多少节点了,即为now,以及我们需要抽取的数目m,我们每次可以以m/now的概率来确定是否需要保存当前的节点。但是问题出现了,一个节点在当前如果被保存了那么显然对于越往后的节点越不公平,因为随着now的增加,它们的now'>now,所以显然被选中的概率会变小。

    看到m/now,我们会思考,怎么样保证在now'->now+1后更新所有被选中的概率呢?最好这个因子是now/now',因为这样更新后的每个节点的概率就依然会保持m/now',这样一想就可以这样,每次同样以m/now'选择节点,如果被选中,就将原来被选中的任意一个替换掉。这样一来,每个之前保存的节点不被替换掉的概率是m/now * now/now'=m/now',也就是说继续保持选择状态全部更新为m/mow'。这样随着递增,每个节点被选中的概率一直都是m/now,保证了公平性。

以下是实现中的关键代码:

int PickNode(node *head,int count)
{
	if(head==NULL||count<0)
		return -1;
	node *p=head;
	int total=0;		//记录目前统计的数目
	int *data=new int[count]; //从中抽多少个数据
	int pos;
	int range;	//结果统计区间
	srand((unsigned)time(NULL)); 
	while(p!=NULL)
	{
		total++;
		pos=ulrand()%total;
		if(pos<count)
			data[pos]=p->value;
		p=p->next;
	}
	cout<<"生成完毕,设定统计区间"<<endl;
	cin>>range;

	int temp=total/range;
	int *sta=new int[temp];
	for(int i=0;i<temp;i++)
		sta[i]=0;
	for(int i=0;i<count;i++)
		sta[data[i]/range]++;
	for(int i=0;i<temp;i++)
		cout<<i*range<<"~"<<(i+1)*range<<"段:"<<sta[i]<<endl;
	return 0; 
}

产生大随机数的方法先产生一个小的随机数,然后将产生的随机数组合起来:

unsigned long ulrand(void) {
	return (
		(((unsigned long)rand()<<24)&0xFF000000ul)
		|(((unsigned long)rand()<<12)&0x00FFF000ul)
		|(((unsigned long)rand()    )&0x00000FFFul));
}


先输入队列长度,要选出的元素个数,然后筛选完毕,统计各个数段的个数,看是否均匀。以1千万为长度,然后以50万为区间进行统计。结果如下:

抽取10000个元素:                抽取10万元素:

    

    另外还有一个要注意,在生成随机数的时候,如果直接调用rand(),需要为它指定参数,以得到不同的随机序列,另外他生成的随机数受stdlib.h中定义的RAND_MAX限制,如果要生成足够大的随机数应该生成几次,然后取它的不同位赋给一个新的数,比如第一次生成的低10位对应新数的低10位,第二次对应11~20位,依此类推,这样就可以得到一个足够大的随机数了。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值