算法之洗牌算法(随机置乱算法)

排序算法用于将一个序列变成有序的,而洗牌算法则用于将一个序列打“乱”,可以认为是排序算法相反操作。洗牌算法需要借助随机数实现来打“乱”序列。

什么才是“真的乱”

洗牌算法正确性的判断准则(“乱”的判断依据)有两个:

对于包含n个元素的序列,其全排列有n!种可能。故若序列打乱的结果有n!种且每种出现的概率一样,则是正确的洗牌算法。因打乱结果的种数肯定不大于n!,故反例有两种情况:

打乱结果的种数小于n!:显然此时全排列中的某些结果无法由洗牌算法产生,故此时的洗牌算法不对;

打乱结果的种数等于n!但每种的出现的概率不一样

另一个准则(与上述准则等价):排序算法使得每个元素出现在每个位置的概率一样(即1/n)。 这一准则也可由上述准则得到,任一元素x出现在任一位置i的排列种数为 (n-1)! ,故概率为 (n-1)!/n! = 1/n 。

 

怎样做到“真的乱”

洗牌算法代码通过随机获取元素并交换来产生随机结果。

比较著名、常用且实现很简洁的是 Knuth-Shuffle 算法

原理:将数组分为已打乱和未打乱的前后两部分(初始时两者分别由0、n个元素),每次随机从未打乱部分中选择一个元素加入到已打乱部分中。易得种数为 n! 。

代码:四种写法本质上一样。时间复杂度O(n)

// 得到一个在闭区间 [min, max] 内的随机整数
int randInt(int min, int max);

// 第一种写法
void shuffle(int[] arr) {
    int n = arr.length();
    /******** 区别只有这两行 ********/
    for (int i = 0 ; i < n; i++) {
        // 从 i 到最后随机选一个元素
        int rand = randInt(i, n - 1);
        /*************************/
        swap(arr[i], arr[rand]);
    }
}

// 第二种写法
    for (int i = 0 ; i < n - 1; i++)
        int rand = randInt(i, n - 1);

// 第三种写法
    for (int i = n - 1 ; i >= 0; i--)
        int rand = randInt(0, i);

// 第四种写法
    for (int i = n - 1 ; i > 0; i--)
        int rand = randInt(0, i);
View Code

正确性的证明:对于数组中的任一元素x,其出现在任一位置i的可能性为:x不出现在前 i-1 位置且在第i次出现的概率= { (n-1)/n * (n-2)/(n-1) * ... * (n-i)/[n-(i-1)]  } * 1/(n-i) = 1/n

反例:每次都从整个数组中随机选一个元素交换到首位,进行n轮。虽然结果种数也为n!,但每轮都有n种选择故总结果数为nn(包含重复的结果),由于nn>n! 且前者不能被后者整除故肯定有些排列出现的概率会比较大,从而不是正确的洗牌算法。

如何验证“真的乱”

理论证明

看洗牌算法是否符合前面的两个准则之一。实例见上节证明部分。

实验检验

可以用蒙特卡洛方法来验证,与两个准则对应地,有两种方法验证:各种结果出现的频率是否大致一样,若大致一样则说明洗牌算法是正确的;或者每个元素出现在各位置的概率一样。

法1:给定一个数组如{1,2,3,4,5},进行N次(N最好大些)实验:以该数组为参数调用洗牌算法,记录每次洗牌后的结果并统计。若结果数为n!且各结果出现的频数大致一样则说明正确。

法2:与上面类似,只不过数组中除了一个元素为1外其他元素为0。每次实验只需记录洗牌后1的位置,最后看1出现的位置是否大致一样。

 

实践

“点选文字验证码”的场景

从给定的字符集(如大小写字母、数字、或中文字符)中随机生成n个字符,要求生成的验证码字符串中不允许有字符重复出现。用 Knuth 洗牌算法即可(O(n)复杂度),只不过不用全洗,洗n个即可。

另一个场景:除了返回绘有上述字符的图片,还需返回这些字符的一个随机排序以让用户按这个顺序点选字符。这实际上是要对字符进行一次洗牌。

 

20220225

其他洗牌算法:水塘采样算法,特点是不需要知道元素总数、不需要元素交换。详见 https://www.cnblogs.com/z-sm/p/6589803.html

 

参考资料

https://github.com/labuladong/fucking-algorithm/blob/master/算法思维系列/洗牌算法.md

posted @ 2020-03-02 00:41  March On  阅读(7258)  评论(0编辑  收藏  举报
top last
Welcome user from
(since 2020.6.1)