赞
踩
概率的应用
http://blog.csdn.net/djangobuaa/article/details/51133147这一节可以参考这篇博客,里面对每道题都有详细的解析!!!
1. 利用随机来改进著名算法(快速排序)
2. 随机数发生器(用给定的随机数发生器构造另一个)
案例一:8只球队,有三个强队,其余都是弱队,随机把他们分成4组比赛,每组两个队,问两强相遇的概率是多大?
利用排列组合,分别计算出分子和分母。
1. 首先求出8只球队分成4组比赛的方法数。
7*5*3*1=105
2. 没有两强相遇的方法数。
也就是在5只弱队中选出3只与强队配对,剩下的2只自行配对。
从5只弱队中选出3只弱队的方法数为:C(5,3),3只强队和3只弱队彼此配对的方法数为:A(3.3)
所以结果为:C(5,3)*A(3,3)=60
3. 求两强不相遇的方法数为:
(105-60)/105=3/7;
最大公约数(Greatest Common Divisor,GCD)的算法有两种:
1. 辗转相除法,也叫欧几里得算法。
假设m>n,计算m除以n,将得到的余数记为r。如果r=0,那么n就是求得的最大公约数,否则继续向下执行。
向下执行的过程中,将m和n中较小的数也就是除数n作为被除数,将r作为除数,也就是m=n,n=r,然后重复上面的步骤。知道
余数为0,此时的n就是最大公约数。
2. 更相减损法
3. Stein算法
辗转相除法的缺点:计算机中的整数最多是64位,如果参与运算的整数低于64位,那么取模的算法比较简单,直接用%就可以。对于计算两个超过64位的整数的模,就不能用辗转相除法。因此,在计算超过64位的整数时 ,就要用Stein算法。这个算法不需要除法和取模,而是采用整数的移位和最普通的加减法。
1.如果A=0,B是最大公约数,算法结束
2.如果B=0,A是最大公约数,算法结束
3.设置A1 = A、B1=B和C1 = 1
4.如果An和Bn都是偶数,则An+1 =An /2,Bn+1 =Bn /2,Cn+1 =Cn *2(注意,乘2只要把整数左移一位即可,除2只要把整数右移一位即可)
5.如果An是偶数,Bn不是偶数,则An+1 =An /2,Bn+1 =Bn ,Cn+1 =Cn (很显然啦,2不是奇数的约数)
6.如果Bn是偶数,An不是偶数,则Bn+1 =Bn /2,An+1 =An ,Cn+1 =Cn (很显然啦,2不是奇数的约数)
7.如果An和Bn都不是偶数,则An+1 =|An -Bn|,Bn+1 =min(An,Bn),Cn+1 =Cn
8.n++,转4
9. An+1和Bn+1相等,算法停止。最后的结果就是Cn+1和An+1的乘积。
上面的算法需要用到的两个性质为:
gcd(a,a) = a,也就是一个数和他自身的公约数是其自身 (也就是上面算法停止的条件就是An+1和Bn+1相等!!!!)
gcd(ka,kb) = k gcd(a,b),也就是最大公约数运算和倍乘运算可以交换,特殊的,当k=2时,说明两个偶数的最大公约数必然能被2整除
一个奇数的所有约数都是奇数。
辗转相除法与Stein(更相减损术)的区别
(1)都是求最大公因数的方法,计算上辗转相除法以除法为主,更相减损术以减法为主,计算次数上辗转相除法计算次数相对较少,特别当两个数字大小区别较大时计算次数的区别较明显。
(2)从结果体现形式来看,辗转相除法体现结果是以相除余数为0则得到,而更相减损术则以减数与差相等而得到
class Championship {
public:
vector<int> calc(int k) {
// write code here
//首先计算可能的分组情况
int count=1;
int i=2*k-1;
while(i>=1){
count=count*i;
i--;
i--;
}
//下面计算从k+1个弱队中选出k-1个和强队相遇的情况
int A=k-1;
int B=k+1;
int C=B-A;
int sum=1;
while(B>C){
sum=sum*B;
B--;
}
int sum2=1;
while(A>=1){
sum2=sum2*A;
A--;
}
sum=sum/sum2;
//下面计算把强弱进行配对的情况
int count1=1;
for(int j=k-1;j>=1;j--){
count1=count1*j;
}
count1=count1*sum;
vector<int> result;
int fenzi=count-count1;
int fenmu=count;
//下面是求分子和分母的最大公约数
int temp=gcd(fenzi,fenmu);
if(temp!=0){
fenzi=fenzi/temp;
fenmu=fenmu/temp;
}
result.push_back(fenzi);
result.push_back(fenmu);
return result;
}
int gcd(int x,int y){
int c=1;
int minu;
if(x==0&&y!=0)
return y;
if(x!=0&&y==0)
return x;
if(x==0&&y==0)
return 0;
while(x!=0&&y!=0){
if(x%2==0&&y%2==0){
x=x>>1;//不是x>>2!!!!!
y=y>>1;
c=c<<1;
}
else if(x%2==0&&y%2!=0){
x=x>>1;
}
else if(x%2!=0&&y%2==0){
y=y>>1;
}
else if(x%2!=0&&y%2!=0){
minu=x<y?x:y;
x=abs(x-y);
y=minu;
}
}
return(c*y);;//如果两个数没有最大公约数,那么就返回1
}
};
需要注意的是上面的gcd求最大公约数的函数中,左移和右移可以代替乘以2和除以2,但是此时左移和右移仅仅移动一位不是两位!!!!
排列:从n个不同元素中,任取m(m≤n)个元素(被取出的元素各不相同),按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列。用符号 p(n,m)表示。 p(n,m)=n(n-1)(n-2)……(n-m+1)= n!/(n-m)!(规定0!=1)
排列与元素的顺序有关,组合与顺序无关。如231与213是两个排列,2+3+1的和与2+1+3的和是一个组合。当m=n时,为全排列
案例二:三只蚂蚁从正三角形的三个定点沿着边移动速度是相同的,问它们碰头的概率是多少?
如果方向并不都相同,则一定会相遇。
每只蚂蚁方向数有2种,一共有3只蚂蚁。
所以总的情况为2*2*2=8种。
只有完全顺时针和完全逆时针这3种情况下不相遇。
所以最后相遇的概率为:(8-2)/8=3/4;
class Ants {
public:
vector<int> collision(int n) {
// write code here
vector<int> result;
int count=n;
int sum=1;
while(count>0){
sum=sum*2;
count--;
}
//计算不碰头的概率
sum=sum/2;
result.push_back(sum-1);
result.push_back(sum);
return result;
}
};
案例三:某地区重男轻女,一个家庭如果生出一个女孩就一直生,直到生出男孩就停止生育。假设一胎只出生一个孩子,问时间足够长后,男女比例是会变为多少?
假设这一地区一共有n个家庭,n/2的家庭第一胎就生出男孩,所以只有1个孩子。
有n/4的家庭先生1女孩,再生1男孩,有2个孩子。
有n/8的家庭先生2女孩,再生1男孩,有3个孩子。
孩子总数为:
n/2+(n/4)*2+(n/8)*3+(n/16)*4...=2*n(等比数列的计算公式Sn=a1(1-q^n)/(1-q))
每个家庭都会有一个男孩,所以2*n的孩子中,男孩数为n,所以女孩数也为n。所以比例依然是1:1。
案例四:给定给一个等概率随机产生1~5的随机函数,除此之外,不能使用任何额外的随机机制,请实现等概率随机产生1~7的随机函数。
1. 已经有等概率随机产生1,2,3,4,5的随机函数。
2.根据步骤1得到的结果减1,将得到f()-1>0 1 2 3 4
3. f()*5->0,5,10,15,20
4. f()*5+(f()-1)>0,1,2,3,4......24
需要注意的是上面的两个f()是独立分别调用的,不要化简。第二个f()是用来填补前面的式子产生的缝隙。
5. 如果步骤4产生的数大于20,则重复进行步骤4,直到产生的结果在0~20之间。
6. 步骤5的结果将等概率随机产生0~20,所以步骤5的结果%7之后等概率产生0~6。(这是因为0~6模7会产生0~6,7~13模7也会得到0~6,14~20模7也会得到0~6!!!正好三套0~6.)
7. 步骤6的结果加1,将等概率产生1~7.
之所以通过上面的方式来产生随机数可以参考http://www.cnblogs.com/luxiaoxun/archive/2012/09/10/2678315.html,里面有详细的解释。
class Random5 {
public:
static int randomNumber();
};
class Random7 {
public:
int rand5() {
return Random5::randomNumber();
}
// 以上内容请不要修改
int randomNumber() {
// 代码写这里,通过rand5函数随机产生[1,7]
int result=5*(rand5()-1)+rand5()-1;
while(result>20){
result=5*(rand5()-1)+rand5()-1;
}
result=result%7+1;
return result;
}
};
案例五;给定一个以p概率产生0,以1-p概率产生1的随机函数f(),p是固定的值,但你并不知道是多少,除此之外也不能使用任何额外的随机机制,请用f()实现等概率随机产生0和1的随机函数。
产生01和10序列的概率都为p*(1-p)
不断调用f,知道能够产生01或10,序列终止。
如果产生了01,返回0;
如果产生了10,则返回1即可。因为两个序列产生的概率是一样的。
class RandomP {
public:
static int f();
};
class Random01 {
public:
// 用RandomP::f()实现等概率的01返回
int random01() {
while(1){
int a=RandomP::f();
int b=RandomP::f();
if(a==0&&b==1){
return 0;
}
if(a==1&&b==0){
return 1;
}
}
return 0;
}
//生成01以及10的概率是一样的,都是p(1-p)
};
案例六:假设函数f()等概率随机返回一个在[0,1)范围上的浮点数,那么在[0,x)区间上的数出现的概率为x(0<x<=1)、给定一个大于0的整数k,并且可以使用
f()函数,请实现一个函数依然返回在[0,1)范围上的数,但是在[0,x)区间上的数出现的概率为x的k次方。
1. 首先,如何把[0,x)范围上的数,从概率x调整为概率x的平方。
做法:调用两次f,返回较大的数即可。
2. 因此本题只要调用k次f,返回较大的数即可。
相当于进行k次伯努利实验,是相互独立的 !!!
class RandomSeg {
public:
// 等概率返回[0,1)
double f() {
return rand() * 1.0 / RAND_MAX;
}
// 通过调用f()来实现
double random(int k, double x) {
//调用k次的f,最后返回最大值!!!
int count=0;
double max=-1;//因为这里生成的随机数是double类型的
double rand;
while(count<k){
rand=f();//不能写多次的f(),写一次就是调用一次f()
if(rand>max)
max=rand;
count++;
}
return max;
}
};
上面的程序中一定更要注意double!!!!所以max以及rand都应该是double类型的!!!
案例七:给定给一个长度为N且没有重复元素的数组arr和一个整数M,实现函数等概率随机打印arr中的M个数。
解析:首先随机生成0~N-1之间的数然后打印,然后将这个数和数组最后一个元素交换位置,然后随机生成0~N-2之间的数打印,然后将这个数和数组倒数第二个元素交换位置,然后以此类推直到打印出M个数。
需要掌握的技巧:很多有关等概率的问题都可以利用和最后一个元素交换的做法!!!类似于抽签问题!!!
class RandomPrint {
public:
vector<int> print(vector<int> arr, int N, int M) {
// write code here
int count=1;
vector<int> result;
int index;
while(count<=M){
index=rand()%N;//随机生成0-N-1之间的数
result.push_back(arr[index]);
arr[index]=arr[index]^arr[N-1];
arr[N-1]=arr[index]^arr[N-1];
arr[index]=arr[index]^arr[N-1];
N--;
count++;
}
return result;
}
};
上面的算法中用异或来完成数组元素之间的交换位置!!
案例八:有一个机器按自然数序列的方式吐出球,1号球,2号球,等等。有一个袋子,袋子里最多只能装下k个球,并且除袋子以外,没有更多的空间。一个球一旦扔掉,就再也不可拿回。设计一种选择方式,使得当机器吐出第N号球的时候,袋子中的球数量是k个,同时可以保证从1号球到N号球中的每一个,被选进袋子的概率都是(k/n)。
解析:此题核心解法为蓄水池抽样算法,过程如下:
1. 处理1~k号球的时候,直接放进袋子里。
2. 处理第i号球时,以k/i的概率决定是否将第i号球放进袋子。如果不决定将第i号球放进袋子,直接扔掉第i号球。如果决定将第i号球放进袋子,那就从袋子里的k个球中随机扔掉一个,然后把第i号球放入袋子。
3. 处理第i+1号球时,重复步骤1或者步骤2.
但是,上面的过程是如何保证1~N号球每一个被选进袋子的概率都是(k/n)呢?下面是证明的过程:
综上,在i<=k时第i号球被放进袋子的概率也是k/N.。所以证明1~N号球每一个被选进袋子的概率都是(k/n).
class Bag {
public:
vector<int> ret;
// 每次拿一个球都会调用这个函数,N表示第i次调用
vector<int> carryBalls(int N, int k) {
if(ret.size() < k){
ret.push_back(N);
}
else{
//第N个球不放进袋子的概率为1-k/N
if((rand()%N+1)<=k)//第N个球要放进袋子
{
int temp=rand()%k;//要删除的第temp个球,换成N
ret[temp]=N;
}
//从袋子中随机选择一个数
}
return ret;
}
};
上面的实现中,最难的是if((rand()%N+1)<=k),这个判断是说又(k/N)的概率执行这个if里面的内容!!!!
c++中如何随机生成0~1之间的浮点数,需要注意的是c++中rand()函数是随机生成一个整数!!rand()不需要参数,它会返回一个从0到最大随机数的任意整数,最大随机数的大小通常是固定的一个大整数。
C++中用来产生随机数的函数为rand(), 其返回值为整数。
而0到1之间的随机数,明显是浮点数。
这样无法直接产生。
于是,必须经过转换。
转换思路为,生成一个0-N的随机数,然后对其除以N+1,则可以得到0-1直接的浮点型随机数。
N的确定,可以与要求的精度有关,比如需要三位小数,可以采用N=999,如需要四位,则采用N=9999,以此类推。
具体操作如下:
1
2
3
4
5
6
7
8
9
10
|
#include <cstdlib>
#include <ctime>
void
get_rand(
float
*p,
int
n)
//函数功能为产生n个0-1的随机数,存储于数组p中。
{
int
i;
#define N 999
//三位小数。
srand
(
time
(NULL));
//设置随机数种子,使每次获取的随机序列不同。
for
(i = 0; i < n; i ++)
p[i] =
rand
()%(N+1)/(
float
)(N+1);
//生成0-1间的随机数。
}
|
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。