随机打乱数组算法、蓄水池算法

1.随机打乱数组(洗牌算法)

  分析洗牌算法正确性的准则:产生的结果必须有 n! 种可能,否则就是错误的。这个很好解释,因为一个长度为 n 的数组的全排列就有 n! 种,也就是说打乱结果总共有 n! 种。算法必须能够反映这个事实,才是正确的。

代码:

def shuffleArr(arr):
    l = len(arr)
    for i in range(l):
        rand = random.randint(i,l-1)
        arr[i],arr[rand] = arr[rand],arr[i]
    print(arr)

arr = [1,2,3,4,5]
shuffleArr(arr)

  

2.蓄水池算法

应用场景:长度未知的海量数据流中随机等概率抽取一个数据

算法过程:

  假设数据序列的规模为n,需要采样的数量的为k。

  首先构建一个可容纳k个元素的数组,将序列的前k个元素放入数组中。

  然后从第k+1个元素开始,以\(\frac{k}{n}\)的概率来决定该元素是否被替换到数组中(数组中的元素被替换的概率是相同的)。 当遍历完所有元素之后,数组中剩下的元素即为所需采取的样本。

证明过程:

对于第i个数(\(i≤k\))。在k步之前,被选中的概率为1。当走到第k+1步时,被k+1个元素替换的概率 = \(k+1\)个元素被选中的概率\(\times\) i被选中替换的概率,即为\(\frac{k}{k+1} \times \frac{1}{k}\)。则被保留的概率为\(1-\frac{1}{k+1}=\frac{k}{k+1}\)。依次类推,不被k+2个元素替换的概率为\(1 - \frac{k}{k + 2} \times \frac{1}{k} = \frac{k + 1}{k + 2}\)。则运行到第n步时,被保留的概率 = 被选中的概率 * 不被替换的概率,即:

\(1 \times \frac{k}{k + 1} \times \frac{k + 1}{k + 2} \times \frac{k + 2}{k + 3} \times … \times \frac{n - 1}{n} = \frac{k}{n}\)

对于第j个数(\(j>k\))。在第j步被选中的概率为\(\frac{k}{j}\)。不被j+1个元素替换的概率为\(1 - \frac{k}{j + 1} \times \frac{1}{k} = \frac{j}{j + 1}\)。则运行到第n步时,被保留的概率 = 被选中的概率 \(\times\)不被替换的概率,即: \(\frac{k}{j} \times \frac{j}{j + 1} \times \frac{j + 1}{j + 2} \times \frac{j + 2}{j + 3} \times ... \times \frac{n - 1}{n} = \frac{k}{n}\) 所以对于其中每个元素,被保留的概率都为\(\frac{k}{n}\).

代码:

import random
def ReservoirSamplingTest(k):
    N = 10
    pool = [i for i in range(N)]
    result = pool[:k]
    for i in range(k,N):
        r = random.randint(0,i+1)
        if r < k:
            result[r] = pool[i]
    return result

res = ReservoirSamplingTest(4)
print(res)

  

参考文献:

【1】洗牌算法深度详解 - 打乱数组 - 力扣(LeetCode)

【2】蓄水池采样算法(Reservoir Sampling) - alfred_zhong - 博客园

posted @ 2019-09-18 20:59  nxf_rabbit75  阅读(1241)  评论(0编辑  收藏  举报