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

来源:互联网 发布:安知玉如意txt下载久久 编辑:程序博客网 时间:2024/05/17 09:03
一、常考题型
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
最大公约数的计算

classChampionship {
public:    
    intgetGcd(inta,intb) { // 辗转相除法计算最大公约数
        if(a == 0)
            returnb;
        if(b == 0)
            returna;
        inttemp;
        while(b != 0) {
            temp = a % b;
            a = b;
            b = temp;
        }
        returna;
    }
     
    vector<int> calc(intk) {
        intn1 = 1,n2 = 1;
        vector<int> res;
        for(inti = 2*k-1;i >= 3;i = i-2)
            n1 *= i;       
        n2 = (k+1)*k/2;
        for(intj = k-1;j >= 1;j--)
            n2 *= j;
        n2 = n1-n2;
        intgcd = getGcd(n1,n2);       
        res.push_back(n2/gcd);
        res.push_back(n1/gcd);
        returnres;
    }
};

注:最大公约数的另一种计算方法(移位)
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算法。
classAnts {
public:
    vector<int> collision(intn) {
        intC,D;
        C = 1; D = 1<< (n - 1);
        vector<int> res;
        res.push_back(D - C); res.push_back(D);
        returnres;
    }
};

解法二:按照常规的步骤,用快速幂算法求幂次,用移位算法求gcd
classAnts {
public:
    intquickPower(inta,intn) {// 快速幂算法
        longres = 1;
        longbase = a;
        while(n) {
            if(n&1== 1)
                res *= base;
            base *= base;
            n >>= 1;
        }
        return(int)res;
    }
     
    intgetGcd(inta,intb) {// 求gcd的移位算法
        if(a ==0)
            returnb;
        if(b == 0)
            returna;
        if(!(a&1) && !(b&1))
            returngetGcd(a>>1,b>>1) << 1;
        elseif(!(a&1))
            returngetGcd(a>>1,b);
        elseif(!(b&1))
            returngetGcd(a,b>>1);
        else
            returngetGcd(abs(a-b),min(a,b));
    }
     
    vector<int> collision(intn) {
        vector<int> res(2,0);
        intn1 = quickPower(2,n);
        intn2 = n1-2;
        intgcd = getGcd(n1,n2);
        res[0] = n2/gcd;
        res[1] = n1/gcd;
        returnres;
    }
};

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<x≤1)。给定一个大于0的整数k,并且可以使用f()函数,请实现一个函数依然返回在[0,1)范围上的数,但是在[0,x)区间上的数出现的概率为x的k次方。
解法:巧用独立重复试验连续调用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<k;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++解法

1 0
原创粉丝点击