程序员面试金典——解题总结: 9.18高难度题 18.3编写一个方法,从大小为n的数组中随机选出m个整数。要求每个元素被选中的概率相同。

#include 
#include 
#include 

using namespace std;
/*
问题:编写一个方法,从大小为n的数组中随机选出m个整数。要求每个元素被选中的概率相同。
分析:选择。第一想到的是线性选择和划分。如果用快排中的划分,每次可以选择出一个数,
      重复m次,即可。但是不知道是否每个元素被选中的概率相同。
书上解法:
蓄水池抽样问题。
算法思想: 步骤1:设原始数组为A,初始化一个大小为m的数组B,新数组B中每个元素等于原始数组A中对应下标的每个元素
           步骤2:令x从下标m+1到n,生成一个在0~x范围内的随机数k,该随机数用于被替换的元素下标,如果k <= m,说明前面m个元素中有某个元素下标k
		          被选中,则令 B[k] = A[x]
证明等概率:一个例子,首先假设k=100,n=200,对于第101个元素i,我们知道前面i个元素中出现元素i的概率为k/i,即 100/101
           假设m < i < n,需要证明对于任意i有,当以第 m/(i+1)的概率选中元素(i+1)进行替换的时候【即元素i+1有 m/(i+1)的概率出现在蓄水池中时】,
		   前面i个元素出现在蓄水池的概率也是m/(i+1)。
		   证明:前i个元素出现蓄水池的概率P = 在第i+1次选择前出现在蓄水池的概率P1 * 第i+1次选择的时候不被替换的概率P2
		         P1 = 前i次出现在蓄水池的概率= m/i
				 P2 = 1 - 第 i+1 次选择替换的概率 = 1 - 1/(i+1) = i / (i+1)
				 所以P = P1 * P2 = (m/i) * ( i /(i+1) ) = m / (i+1)
				 综上所述,上述蓄水池抽样算法符合题目要求。

输入:
5(数组中需要挑选的元素个数m) 10(数组元素个数n)
1 8 7 5 9 0 3 6 4 2(数组中n个元素)
输出:
m个不重复元素

关键:
1蓄水池抽样问题。
算法思想: 步骤1:设原始数组为A,初始化一个大小为m的数组B,新数组B中每个元素等于原始数组A中对应下标的每个元素
           步骤2:令x从下标m+1到n,生成一个在0~x范围内的随机数k,该随机数用于被替换的元素下标,如果k <= m,说明前面m个元素中有某个元素下标k
		          被选中,则令 B[k] = A[x]
2 证明等概率:一个例子,首先假设k=100,n=200,对于第101个元素i,我们知道前面i个元素中出现元素i的概率为k/i,即 100/101
           假设m < i < n,需要证明对于任意i有,当以第 m/(i+1)的概率选中元素(i+1)进行替换的时候【即元素i+1有 m/(i+1)的概率出现在蓄水池中时】,
		   前面i个元素出现在蓄水池的概率也是m/(i+1)。
		   证明:前i个元素出现蓄水池的概率P = 在第i+1次选择前出现在蓄水池的概率P1 * 第i+1次选择的时候不被替换的概率P2
		         P1 = 前i次出现在蓄水池的概率= m/i
				 P2 = 1 - 第 i+1 次选择替换的概率 = 1 - 1/(i+1) = i / (i+1)
				 所以P = P1 * P2 = (m/i) * ( i /(i+1) ) = m / (i+1)
				 综上所述,上述蓄水池抽样算法符合题目要求。
*/

int randMinMax(int min , int max)
{
	if(min > max)
	{
		throw("min greater than max,false");
	}
	return ( rand() % (max - min + 1) + min );
}

vector selectResults(vector& datas , int selectNum)
{
	if(datas.empty())
	{
		vector results;
		return results;
	}
	int size = datas.size();
	if(size < selectNum)
	{
		vector results;
		return results;
	}

	vector results(selectNum , 0);//初始化数组,长度为selectNum,每个元素为0
	for(int i = 0 ; i < selectNum ; i++)//接下来先填充前面selectNum个元素
	{
		results.at(i) = datas.at(i);
	}

	//接下来就是对于i属于[selctNum,size] 的元素以等概率slectNum/i替换前面的元素
	for(int i = selectNum ; i < size ; i++  )
	{
		//生成0~i的随机数k作为被替换的下标,要满足k < selectNum
		int replaceIndex = randMinMax(0 , i);
		if(replaceIndex < selectNum)
		{
			results.at(replaceIndex) = datas.at(i);  
		}
	}
	return results;
}

void print(vector& datas)
{
	if(datas.empty())
	{
		cout << "No result" << endl;
		return;
	}
	int size = datas.size();
	for(int i = 0; i < size ; i++)
	{
		cout << datas.at(i) << " ";
	}
	cout << endl;
}

void process()
{
	int selectNum , arraySize;
	vector datas;
	vector results;
	int value;
	while(cin >> selectNum >> arraySize)
	{
		datas.clear();
		for(int i = 0 ; i < arraySize ; i++)
		{
			cin >> value;
			datas.push_back(value);
		}
		results = selectResults(datas , selectNum);
		print(results);
	}
}

int main(int argc , char* argv[])
{
	process();
	getchar();
	return 0;
}

你可能感兴趣的:(程序员面试金典)