从n个数中随机选取m个

咋一看,这是个很简单的问题,但是如果n是个不确定的数呢?比如服务器每天会收到数以亿计的请求,但是目前服务器端不希望保存所有的请求,只想随机保存这些请求中的m个。试设计一种算法,能够使服务器实时保存m个请求,并使这些请求是从所有请求中的大致等概率被选中的结果。注意:不到一天的结束,是不能提前知道当天所有请求数n是多少的。下面我们分两种情况讨论(1)n已知,(2)n未知。

 

1 n已知

可以将问题简化为:从集合A(a_1, a_2, … ,a_n),中随机选取m(0≤m≤n)个元素,使得每个数被选取的概率相等。可以很简单的计算每个数被选取的概率是m/n。如果集合A里面的元素本来就具有随机性, 每个元素在各个位置上出现的概率相等, 并且只在A上选取一次数据,那么直接返回A的前面m个元素就可以了, 或者可以采取每隔k个元素取一个等类似的方法。这样的算法局限很大, 对集合A的要求很高。

 

假设集合A中的元素在各个位置上不具有随机性, 比如已经按某种方式排序了,那么我们可以遍历集合A中的每一个元素a_i,根据一定的概率选取a_i,这个概率是多少呢,设m’为还需要从A中选取的元素个数, n’为元素a_i及其右边的元素个数, 也即n’=(n-i+1)。那么选取元素a_i的概率为 m’/n’。这个证明较复杂,下面简单验证一下前两个元素被选中的概率:(设p(a_i=1)表示a_i被选中的概率,p(a_i=0)表示a_i没有被选中的概率)

 

(1)很显然 p(a_1=1)=m/n

(2)p(a_2=1)= p(a_2=1,a_1=1)+p(a_2=1,a_1=0)
= p(a_1=1)*p(a_2=1│a_1=1)+ p(a_1=0)* p(a_2=1│a_1=0)
= m/n * (m-1)/(n-1) + (n-m)/n*m/(n-1)
= m/n

实际编程中选取某个元素时,可以生成一个[0,1]之间的随机数k, 若k<=m'/n'则选取这个元素,否则抛弃。

 

2 n未知

这个问题可以简化为:一个整数序列生成器,以一定时间间隔生成一个新的整数,一天之内会生成N个,希望实时保存m个整数,使得任何时刻这m个整数都是当前已生成的所有整数数量n中等概率抽取的结果,即概率均为m/n。由于n是未知的,我们需要以某种特殊的方式进行判决保存还是不保存,以使得满足概率要求。具体步骤如下:

(1)对于前m个请求直接保存到服务器上,对应整数序列相当于,整数数组的前m个直接存下来。

(2)对于m个以后的第k个新请求,以m/k的概率选择保存,并同从已保存的m个请求中随机选出的一个进行交换。

细说就是,

    • 对于第m+1个请求,以m/(m+1)的概率选择留下,如果留下了则从已保存的m个请求中随机选出一个,同它交换;
    • 对于第m+2个请求,以m/(m+2)的概率选择留下,如果留下了则从已保存的m个请求中随机选出一个,同它交换;
    • 对于第m+3个请求,以m/(m+3)的概率选择留下,如果留下了则从已保存的m个请求中随机选出一个,同它交换;

            …

下面我们用数学归纳法证明这个方法使每个元素被选取的概率是m/n:

(1)当n=m+1时,
对于第m+1个请求以概率m/(m+1)选择留下,显然满足m/n的要求;
对于前m个请求中的任何一个,能被选择留下有两种情况:a.第m+1个请求被选择留下了并且没有和自己进行交换; b.第m+1个请求没有被选择留下来而自己确实已被选择留下来了。
所以,概率计算为 m/(m+1) * (m-1)/m + (1 – m/(m+1)) * 1 = (m-1)/(m+1) + 1/(m+1) = m/(m+1)

(2)假设当n=N时,仍然正确,即任何一个请求被选中的概率都是m/N,现在推到证明当n=N+1时,任何一个请求被选中留下的概率是m/(N+1)。
对于第N+1个请求,因为是以m/n=m/(N+1)的概率选中的,所以显然满足要求;
对于前m个请求中任何一个,能被选中留下同样分为同上的两种情况:a.一种是第N+1个被选中了但随机抽取出与它交换的不是自己;  b.另一种情况是自己已留下并且第N+1个未被选中留下。并且前m个请求中的某个被选中的前提是:在处理完第N个请求后,该请求被选中,根据假设这个概率是m/N。

和概率为: [ m/(N+1) * (m-1)/m + (1-m/(N+1)) ]* m/N = [ (m-1)/(N+1) + (N+1-m)/(N+1) ] * m/N = m/(N+1)。

即当n=N+1时,仍然正确。

综合1)、2)可知,此方法满足等概率要求

当然这种选取方法也适用于n已知的情况。

 

 【版权声明】转载请注明出处 http://www.cnblogs.com/TenosDoIt/p/3364139.html

转载于:https://www.cnblogs.com/TenosDoIt/p/3364139.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 从n个数选出m个数的算法可以使用递归的方法进行实现。首先,我们需要定义一个函数来选择数: ```python def choose_numbers(arr, m, selected_numbers=[]): if m == 0: return [selected_numbers] result = [] for i in range(len(arr)): num = arr[i] remaining_numbers = arr[i+1:] result += choose_numbers(remaining_numbers, m-1, selected_numbers+[num]) return result ``` 在这个函数,`arr`参数是待选择的n个数的列表,`m`参数表示要选择的数的个数,`selected_numbers`参数是已经选择的数的列表。 接下来,我们通过调用这个函数来选择数: ```python n = int(input("请输入n的值:")) m = int(input("请输入m的值:")) arr = [] for i in range(n): arr.append(int(input("请输入第{}个数:".format(i+1)))) selected_numbers = choose_numbers(arr, m) for num_list in selected_numbers: print(num_list) ``` 在这段代码,我们首先通过`input`函数获用户输入的n和m的值。然后,我们通过循环获用户输入的n个数,并将其添加到`arr`列表。最后,我们调用`choose_numbers`函数来选择数,并将结果打印输出。 这个算法会输出所有可能的选择结果。例如,当n=4,m=2时,选择数的结果为:[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]。 注意:由于题目并没有明确要求数是否可以重复选择,所以上述算法允许重复选择同一个数。如果不允许重复选择同一个数,可以在选择时进行判断并排除重复选择的情况。 ### 回答2: 从n个数选m个数的算法可以使用递归的方式来实现。以下是一个用Python编写的递归算法实现示例: ```python def combination(nums, m): result = [] current = [] def helper(start, nums, m): if m == 0: # 如果已选满m个数,则将结果保存到结果列表 result.append(current[:]) return if start >= len(nums): # 如果起始位置超过了数组长度,则返回 return current.append(nums[start]) # 选择当前数 helper(start + 1, nums, m - 1) # 递归调用,继续选择下一个数 current.pop() # 回溯,撤销选择 helper(start + 1, nums, m) # 不选择当前数,继续向后遍历 helper(0, nums, m) return result ``` 使用示例: ```python nums = [1, 2, 3, 4, 5] m = 3 result = combination(nums, m) print(result) # 输出 [[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]] ``` 以上算法通过递归实现了从n个数选取m个数的所有组合情况,并将结果保存在result列表返回。 ### 回答3: 要从n个数选m个数,可以使用回溯法实现。下面是一个用Python编写的示例代码: ```python def combination(nums, m): result = [] # 存放选取的组合结果 path = [] # 存放当前选取的路径 def backtrack(nums, start): if len(path) == m: # 到达指定的选取个数m result.append(path[:]) return for i in range(start, len(nums)): path.append(nums[i]) backtrack(nums, i+1) # 递归进入下一层,注意是 i+1 path.pop() # 回溯,尝试下一个分支 backtrack(nums, 0) # 从索引0开始回溯 return result nums = [1, 2, 3, 4, 5] m = 3 print(combination(nums, m)) ``` 以上代码,`num`表示给定的n个数的列表,`m`表示要选取个数。`result`列表用于存放最终的组合结果,`path`列表用于存放当前选取的路径。 在`backtrack`函数,首先判断当前选取个数是否达到目标个数`m`,如果是则将当前路径加入结果列表,否则进行递归。 在每一层递归,从当前索引`start`开始遍历`nums`列表,将当前元素加入路径,并递归进入下一层。递归返回后,将当前元素从路径移除,进行回溯,继续尝试下一个分支。 最后调用`combination`函数并输出结果,即可得到从n个数选m个数的所有组合。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值