STL系列十一 随机三趣题——随机重排,文件中随机取一行,生成N个随机数

本文将介绍三个有趣的随机问题,分别是随机重新排列、从文件中随机取一行数据、生成N个随机数。

一.随机重新排列

将一个序列打乱并对其进行随机的重新排列,关键在于每种序列的被选择概率要一样,不然有失“公平”。现在让我们来寻找如何保证每种序列被选择的概率一样大的算法。

首先假设这个数组只有二个元素,设数组a为{1, 2},显然这个数组只有二种可能的排列,要么是{1,2}要么是{2,1}。很容易想到一种方法——只要第二个元素有50%的概率与第一个元素交换即可。用代码表现下:

if (rand() % 2 == 0)

swap(a[0], a[1])

由于rand() % 2的结果要么为0,要么为1,且各占50%的概率。因此swap(a[0], a[1])的执行概率也是50%,如果执行了,结果会是{2,1}。没有执行,结果会是{1,2}。所以这样两种排列出现的可能均为50%。

接下来再假充这个数组有三个元素设数组a为{1, 2,3},这个数组有六种可能的排列,要么是{1,2,3}、{1,3,2}、{2,1,3}、{2,3,1}、{3,1,2}和{3,2,1}。这么多的排列看起来好象有点复杂,不知道从何下手。其实结合上面的分析,我们可以这样考虑:

1.先调整前二个元素即{1, 2,3}先生成{1,2,3}或{2,1,3}。

2.然后对{1,2,3},第三个元素以1/3的概率与第一,第二,第三个元素进行交换就可以等概率的得到{3,2,1}、{1,3,2}和{1,2,3}。

3.同理对{2,1,3},可以等概率的得到{3,1,2}、{2,3,1}和{2,1,3}。

根据贝叶斯公式,不难计算出由这些排列出现的可能性都是1/2 * 1/3 = 1/6。完全符合每种序列的被选择概率相同的要求。

这样随机重新排列的算法就找到了,下面给出示意代码:

//随机重新排列
//参照STL中的random_shuffle
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
inline void Swap(int *a, int *b)
{
	int c = *a;
	*a = *b;
	*b = c;
}
//随机重新排列函数
void Random_shuffle(int a[], int n)
{
	srand(time(NULL));
	for (int i = 1; i < n; i++)
		Swap(&a[i], &a[rand() % (i + 1)]);
}
void PrintfIntArray(int a[], int n)
{
	for (int i = 0; i < n; i++)
		printf("%d ", a[i]);
	putchar(‘\n‘);
}
int main()
{
	printf("           随机重新排列 \n");
	printf("--- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---\n\n");

	const int MAXN = 8;
	int a[MAXN] = {1, 2, 3, 4, 5, 6, 7, 8};

	printf("原数组:\n");
	PrintfIntArray(a, MAXN);

	Random_shuffle(a, MAXN);

	printf("随机重新排列后:\n");
	PrintfIntArray(a, MAXN);
	return 0;
}

运行结果如下:

有兴趣的童鞋可以用STL中的random_shuffle()来改写上面的程序,相信知道其实现原理后会对random_shuffle()有更深的认识。

另外,这种按顺序先后来交换元素得到新排列的方法与生成全排列非常类似,可以参考《STL系列之十 全排列(百度迅雷笔试题)》然后对比下两种方法在思路上相似之处。

二.从文件中随机取一行数据

如果先统计文件有多少行,再根据rand() % 行数选择对应行也是可以行的,但效率显然会有点低了。有没有一种方法可以只遍历文件一次了?请看代码:

//从文件中取机选取一行
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
	printf("           从文件中取机选取一行 \n");
	printf("--- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---\n\n");

	int i, num, nChooseNum;
	const char strFileName[] = "in.txt";

	freopen(strFileName, "r", stdin);
	srand(time(NULL));
	i = 1;
	while (scanf("%d", &num) != EOF)
	{
		if (rand() % i == 0)
			nChooseNum = num;
		i++;
	}
	printf("从文件中选取出: %d\n", nChooseNum);
	return 0;
}

运行结果如下(in.txt中为0到9,每个数字占一行):

对代码进行下讲解,以三行数据为例,首先对文本的第一行,rand() % 1,结果必然为0。所以第一行已被选中了,然后对第二行,rand()%2,结果要么为0,要么为1。故第二行有 50的可能性被选中,然后对第三行,rand()%3,显然被选中的概率为1/3。故有:

选中第一行的概率为1 * 1/2 * 2/3 = 1/3。

选中第二行的概率为1/2 * 2/3 = 1/3。

选中第三行的概率为1/3。

故每一行被选中是等概率的。

三.生成N个随机数

这个有很多方法,常见方法用set记录已经生成的数据,然后判断set的大小是否符合要求。代码如下所示:

//生成n个指定范围的随机数
#include <stdio.h>
#include <time.h>
#include <set>
//生成n个[s, e)范围的数
void GetRandNumberInRange(int *a, int n, int s, int e)
{
	int   i, j;
	std::set<int>  m;
	std::set<int>::iterator setpos;

	srand(time(NULL));
	while (m.size() < n)
	{
		j = rand() % (e - s) + s;
		m.insert(j);
	}

	i = 0;
	for (setpos = m.begin(); setpos != m.end(); setpos++)
		a[i++] = *setpos;
}
void PrintfIntArray(int a[], int n)
{
	for (int i = 0; i < n; i++)
		printf("%d ", a[i]);
	putchar(‘\n‘);
}
int main()
{
	const int NMAX = 20;
	const int NUMSTART = 1;
	const int NUMEND = 100;

	printf("           生成%d个%d到%d之间的数 \n", NMAX, NUMSTART, NUMEND);
	printf("--- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---\n\n");	

	int    a[NMAX];

	GetRandNumberInRange(a, NMAX, NUMSTART, NUMEND + 1);
	PrintfIntArray(a, NMAX);
	return 0;
}

运行结果如下:

这种方法会导致调用随机函数次数过多,从而效率低下。可以采用一种改进的方法,如要生成3个1到10之间的数,取10-3=7,因此可以先t=rand()%7,保存t,然后再t=rand()%8,如果t这个数已经存在就取8保存,再t=rand()%9,同样如果t这个数已经存在就取9保存。这样只会调用3次随机函数。当然这种方法不能完全保证每个数字被选择概率相同,算是牺牲“公平”来保证效率吧。

生成N个随机数的改进方法代码如下:

//生成n个指定范围的随机数
#include <set>
#include <cstdio>
#include <cstdlib>
#include <ctime>
//在[s, e)区间上随机取n个数并存放到a[]中
void GetRandomNum(int *a, int n, int s, int e)
{
	std::set<int> set_a;
	srand(time(NULL));
	for (int i = e - n; i < e; i++)
	{
		int num = (rand() % (i - s)) + s;
		if (set_a.find(num) == set_a.end())
			set_a.insert(num);
		else
			set_a.insert(i);
	}
	i = 0;
	std::set<int>::iterator pos;
	for (pos = set_a.begin(); pos != set_a.end(); pos++)
		a[i++] = *pos;
}
void PrintfIntArray(int a[], int n)
{
	for (int i = 0; i < n; i++)
		printf("%d ", a[i]);
	putchar(‘\n‘);
}
int main()
{
	const int NMAX = 20;
	const int NUMSTART = 1;
	const int NUMEND = 100;

	printf("           生成%d个%d到%d之间的数 改进版\n", NMAX, NUMSTART, NUMEND);
	printf("--- by MoreWindows( http://blog.csdn.net/MoreWindows )  ---\n\n");
	int a[NMAX];

	GetRandomNum(a, NMAX, NUMSTART, NUMEND + 1);
	PrintfIntArray(a, NMAX);
	return 0;
}

运行结果如下:

有关随机的趣味题就介绍到这里,有任何问题欢迎和我交流,直接留言或发送邮件均可。邮箱:[email protected]

下一篇《STL系列十二链表排序的6次改进(对《STL源码剖析》作个补充)》将会对STL中的链表排序进行详细讲解,如果看过《STL源码剖析》书中142页的链表排序代码将会更有收获。更多内容,请继续关注MoreWindows

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7659532

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://www.cnblogs.com/captainbed

原文地址:https://www.cnblogs.com/heishanglaoyao/p/10241008.html

时间: 2024-08-14 16:24:55

STL系列十一 随机三趣题——随机重排,文件中随机取一行,生成N个随机数的相关文章

perl实现从一个文件中随机抽取n行

源码之前,了无秘密,直接上源码: if(@ARGV!=2){ die "Usage: $0 sample_file sample_number\n"; } my($sample_file, $sample_number) = @ARGV; open my $FILE, "<$sample_file" or die "Can't open $sample_file $!\n"; my @content = <$FILE>; my

awk: 随机替换(使用文件中的行随机替换另一文件中的列)

#!/bin/awk -f #Function:指定使用文件中随机数量的行(手动传参ipl和idl)随机替换指定列(脚本中指定):使用指定位数的随机浮点替换指定列(脚本中指定). #Usage:./t.awk -v ip=<ip file> -v id=<id file> -v ipl=<n> -v idl=<n> log.txt #Date: 20140922 20:16 BEGIN {     srand()     #读取预用的ip列表     whi

STL系列之六 set与hash set

STL系列之六 set与hash_set set和hash_set是STL中比较重要的容器,有必要对其进行深入了解.在STL中,set是以红黑树(RB-tree)作为底层数据结构的,hash_set是以Hash table(哈希表)作为底层数据结构的.set可以在时间复杂度为O(logN)情况下插入.删除和查找数据.hash_set操作的时间复杂度则比较复杂,这取决于哈希函数和哈希表的负载情况.下面列出set和hash_set的常用函数: set和hase_set的更多函数请查阅MSDN. se

mysql 在数据表中随机取数据或随机排序

最近遇到一个需求,就是随机取数据,本着拿来主义的精神,网上找了找,基本上都是这个下面这个内容,但是并不能让人满意: 1.select * from users order by rand() LIMIT 1 2.SELECT * FROM users  AS t1  JOIN (SELECT ROUND(RAND() * ((SELECT MAX(userId) FROM `users`)-(SELECT MIN(userId) FROM users))+(SELECT MIN(userId)

Nginx系列教程(三)| 一文带你读懂 Nginx 的负载均衡

作者:JackTian 微信公众号:杰哥的IT之旅(ID:Jake_Internet) LAMP 系列导读 01. LAMP 系列教程(一)| 详解 Linux 环境下部署 HTTPD 服务 02. LAMP 系列教程(二)| 如何在 Linux 环境下部署 AWStats 分析系统来监控 Web 站点? 03. LAMP 系列教程(三)| 一文读懂 HTTPD 服务的访问控制 04. LAMP 系列教程(四)| MySQL 数据库系统(一) 05. LAMP 系列教程(五)| MySQL 数据

Java知多少(72)文件的随机读写

Java.io 包提供了 RandomAccessFile 类用于随机文件的创建和访问.使用这个类,可以跳转到文件的任意位置读写数据.程序可以在随机文件中插入数据,而不会破坏该文件的其他数据.此外,程序也可以更新或删除先前存储的数据,而不用重写整个文件. RandomAccessFile类是Object类的直接子类,包含两个主要的构造方法用来创 建RandomAccessFile 的对象,如表 10-11 所示. 表 10-11 RandomAccessFile 类的构造方法 构造方法 功能描述

序列相关的趣题 之四

(8) 给定一个英文单词,消除其中重复的字母,只能删掉字母,不能交换字母顺序,最后原单词中每个字母只出现一次,求字典序最小的结果. 这是toj一个题,百度面试也问过,原题见 http://acm.tju.edu.cn/toj/showp3257.html 此题我非常喜欢,巧妙之处是其算法是O(n)的-- .我们一个字母一个字母加入序列,一旦来了一个比较"小"的字母,因为我们需要字典顺序最小,我们希望它尽可能靠前.所以我们试图"冒泡"似的把小的往前面送,经过尾部那些较

SQL Server 2008空间数据应用系列十一:提取MapInfo地图数据中的空间数据解决方案

原文:SQL Server 2008空间数据应用系列十一:提取MapInfo地图数据中的空间数据解决方案 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测. 2.具备 Transact-SQL 编程经验和使用 SQL Server Management Studio 的经验. 3.熟悉或了解Microsoft SQL Server 2008中的空间数据类型. 4.具备相应(比如OGC规范.KML规范)的GIS专业理论知识. 5

在ASP.NET MVC应用程序中随机获取一个字符串

在开发ASP.NET MVC应用程序时,有可能需要一个随机字符串,作为密码或是验证码等. 如果你需要的是SQL版本,可以参考<密码需要带特殊字符(二)>http://www.cnblogs.com/insus/archive/2012/02/16/2354453.html 此篇实现方法多少是参照这个实现C#版本. 在应用程序下,创建一个CharacterUtility.cs: 这个类别中,分别有几个静态方法:一,为随机的小写字母: 二,是随机产生大写字母: 三,是随机产生数字: 四,是产生特殊