与概率相关的算法题C++解法(附证明过程)

一、常考题型
1、客观题(选择题);
2、古典概率、期望的计算,不涉及高等概率和微积分;
3、利用随机来改进著名的算法(快速排序);
4、随机数发生器(根据给定的随机数发生器构造另一个)。
二、练习题
1、 有2k只球队,有k-1个强队,其余都是弱队,随机把它们分成k组比赛,每组两个队,问两强相遇的概率是多大?结果化成最简分数。
解法:该题的难点有两点:
总组队方法数的计算。用C(2k,2)C(2(k-1),2)……C(2,2)计算是错误的 ,因为这样把选取其中任意两队为一组重复计数了k次,例如4支球队A,B,C,D,选AB,则CD自然为一组,若用C(4,2)计算,它把先选AB和先选CD算成了两种情况,实际上是同一种情况。所以应该是C(2k,2)C(2(k-1),2)……C(2,2)/k*(k-1)……2*1
最大公约数的计算

class Championship {
public :      
     int getGcd( int a, int b) {  // 辗转相除法计算最大公约数
         if (a ==  0 )
             return b;
         if (b ==  0 )
             return a;
         int temp;
         while (b !=  0 ) {
             temp = a % b;
             a = b;
             b = temp;
         }
         return a;
     }
     
     vector< int > calc( int k) {
         int n1 =  1 ,n2 =  1 ;
         vector< int > res;
         for ( int i =  2 *k- 1 ;i >=  3 ;i = i- 2 )
             n1 *= i;       
         n2 = (k+ 1 )*k/ 2 ;
         for ( int j = k- 1 ;j >=  1 ;j--)
             n2 *= j;
         n2 = n1-n2;
         int gcd = getGcd(n1,n2);       
         res.push_back(n2/gcd);
         res.push_back(n1/gcd);
         return res;
     }
};

注:最大公约数的另一种计算方法(移位)
int  getGcd( int  a, int  b) {
         if  (a ==  0
            return  b;
         if  (b ==  0
            return  a;
         if  (!(a &  1 ) && !(b &  1 )) 
            return  getGcd(a>> 1 , b>> 1 ) <<  1 ;
         else if  (!(b &  1 )) 
            return  getGcd(a, b>> 1 );
         else if  (!(a &  1 )) 
            return  getGcd(a>> 1 , b);
         else 
            return  getGcd(abs(a - b), min(a, b));
}


2、n只蚂蚁从正n边形的n个定点沿着边移动,速度是相同的,问它们碰头的概率是多少?结果化为最简分数。
解法:所有情况有2的n次方种,不碰撞只有两种,即所有蚂蚁均顺时针或均逆时针。碰撞概率为(pow(2,n)-2)/pow(2,n)=(pow(2,n-1)-1)/pow(2,-1n)。
解法一:由于是2的幂次,可以直接移位来解决。又由于分子是2的幂次减1,分母是2的幂次,必然不可再约分。所以无需快速幂算法和移位求gcd算法。
class Ants {
public :
     vector< int > collision( int n) {
         int C,D;
         C =  1 ; D =  1 << (n -  1 );
         vector< int > res;
         res.push_back(D - C); res.push_back(D);
         return res;
     }
};

解法二:按照常规的步骤,用 快速幂算法 求幂次,用 移位算法求gcd
class Ants {
public :
     int quickPower( int a, int n) {// 快速幂算法
         long res =  1 ;
         long base = a;
         while (n) {
             if (n& 1 ==  1 )
                 res *= base;
             base *= base;
             n >>=  1 ;
         }
         return ( int )res;
     }
     
     int getGcd( int a, int b) {// 求gcd的移位算法
         if (a == 0 )
             return b;
         if (b ==  0 )
             return a;
         if (!(a& 1 ) && !(b& 1 ))
             return getGcd(a>> 1 ,b>> 1 ) <<  1 ;
         else if (!(a& 1 ))
             return getGcd(a>> 1 ,b);
         else if (!(b& 1 ))
             return getGcd(a,b>> 1 );
         else
             return getGcd(abs(a-b),min(a,b));
     }
     
     vector< int > collision( int n) {
         vector< int > res( 2 , 0 );
         int n1 = quickPower( 2 ,n);
         int n2 = n1- 2 ;
         int gcd = getGcd(n1,n2);
         res[ 0 ] = n2/gcd;
         res[ 1 ] = n1/gcd;
         return res;
     }
};

3、给定一个等概率随机产生1~5的随机函数,不使用任何额外的随机机制,请实现等概率随机产生1~7的随机函数
解法:此题没做过的确很难。 推导过程 如下。
推导过程.docx
class  Random7 {
public :
     int  rand5() {
         return  Random5::randomNumber();
     }         
     int  randomNumber() {
         int  temp;
         do {
             temp =  5 *(rand5()- 1 )+(rand5()- 1 );
         while (temp >  20 );           
         return  (temp% 7 )+ 1 ;
     }       
};

4、给定一个以p概率产生0,以1-p概率产生1的随机函数RandomP::f(),不使用任何额外的随机机制,实现等概率随机产生0和1的随机函数。
解法:巧用独立重复试验连续调用两次f()函数,产生01序列和10序列的概率都是p(1-p)。产生01返回0,产生10返回1,产生00和11继续产生,即可。
class  Random01 {
public
:
     int  random01() {
         int  n1,n2;         
         do  {  
            n1 = RandomP::f();
            n2 = RandomP::f();
        } while  (n1 == n2);
         return  n1;
    }
};

5、假设函数f()等概率随机返回一个在[0,1)范围上的浮点数,那么我们知道,在[0,x)区间上的数出现的概率为x(0
解法:巧用独立重复试验连续调用f()函数k次,记录下k次的最大值,将这个最大值返回即可。因为这个最大值落入[0,x)区间的情况只有一种,那就是k次返回值都落入[0,x)区间,落入[0,x)记为1,否则记为0,这就将问题转化为k次独立重复试验,求k次结果均为1的概率,为x的k次方。

class  RandomSeg {
public :
     double  f() {
         return  rand() *  1.0 / RAND_MAX;
     }
     double  random( int  k,  double  x) {
         double  res=- 1 ;
         for  ( int  i= 0 ;i
             res=max(res,f());
             
         }
         return  res;
     }
};

6、给定一个长度为N且没有重复元素的数组arr和一个整数M,实现函数等概率随机打印arr中的M个数
解法:此题与 抽签原理 类似。相当于M个人从N根签中每人抽一根,纵然抽签顺序有先后,但是 每个人抽到某根签的概率是一样的,都是1/N 。因此只需要一个循环,每次随机读取数组中的一个元素,然后把这个元素剔除,下一次循环再随机地从剩下的元素中读取一个,直到读取完M个为止。
class  RandomPrint {
public
:
    vector< int > print(vector< int > arr,  int  N,  int  M) {
        vector< int > res;
         int  t;  
         for  ( int  i =  0 ;i < M;i++) {             
            t = rand()%(N-i); // 这里的随机数产生方法采用的是不指定种子seed的方法
            res.push_back(arr[t]);
            swap(arr[t],arr[N-i- 1 ]);
        }
         return  res;
    }
};


7、 蓄水池抽样 机器吐球)问题
    有一个只能装下10个球的袋子,当吐出100个球时,袋子里有10 球,并且1~100号中的每一个球被选中的概率都是10/100。然后继续吐球,当吐出1000个球时,袋子里有 10 个球,并且1~1000号中的每一个球被选中的概率都是10/1000。继续吐球,当吐出i个球时,袋子里有10个球,并且1~i号中的每一个球被选中的概率都是10/i。也就是随着N的变化,1~N号球被选中的概率动态变化成k/N。设计一个选择函数,输入球的编号N和袋子容量k,返回吐出第N号球后,袋子中k个球的编号。

解法:①显然,吐出前k个球时,所有球都要装入袋子;②当吐出第k+1个球时,由于这k+1个球中每个球被选中的概率都是k/k+1,所以要以k/k+1的概率决定第k+1个球是否被选中;③如果第k+1个球被选中,那么以1/k的概率丢掉袋子中的一个球,再用第k+1个球替换它。
class  Bag {
public
:
    vector< int > ret;
    // 每次拿一个球都会调用这个函数,N表示第i次调用
    vector< int > carryBalls( int  N,  int  k) {
         if  (ret.size() < k)
            ret.push_back(N);
         else  {
             if  (rand()%N < k)
                ret[rand()%k] = N;
        }
         return  ret;
    }
};

推荐博文:

二叉树相关练习题(C++)
经典排序算法的C++实现
与字符串有关的一些典型问题的C++解法
一些可以用动态规划(DP)算法解决的问题(C++)
排列组合相关笔试面试题(C++)
二分查找的巧妙运用(C++)
位运算在算法题中的使用(C++)
链表相关练习题(C++)
用实例讲解栈和队列(C++)
一些智力题的C++解法

你可能感兴趣的:(数据结构与基本算法,概率,C++,最优解法)