zoukankan      html  css  js  c++  java
  • 洗牌算法

    洗牌算法汇总以及测试洗牌程序的正确性

    洗牌可以抽象为:给定一组排列,输出该排列的一个随机组合,本文代码中均以字符数组代表该排列

    算法1-算法3 都是在原序列的基础上进行交换,算法空间复杂度为O(1)

    算法1(错误):随机交换序列中的两张牌,交换n次(n为序列的长度),代码如下:

    复制代码
     1 void Shuffle_randomSwap(char *arr, const int len)
     2 {
     3     for(int i = 1; i <= len; i++)
     4     {
     5         int a = rand()%len;
     6         int b = rand()%len;
     7         char temp = arr[a];
     8         arr[a] = arr[b];
     9         arr[b] = temp;
    10     }
    11 }
    复制代码

    算法2(错误):遍历序列中的每个数,随机选择序列的某个数,把它和当前遍历到的数交换,代码如下:

    复制代码
     1 void Shuffle_FisherYates_change1(char *arr, const int len)
     2 {
     3     for(int i = len - 1; i >= 0; i--)
     4     {
     5         int a = rand()%len;
     6         int temp = arr[i];
     7         arr[i] = arr[a];
     8         arr[a] =  temp;
     9     }
    10 }
    复制代码

    算法3(正确):这是FisherYates洗牌算法,具体可参考wiki,算法的思想是每次从未选中的数字中随机挑选一个加入排列,时间复杂度为O(n),wiki上的伪代码如下

    To shuffle an array a of n elements (indices 0..n-1):
      for i from n − 1 downto 1 do
           j ← random integer with 0 ≤ ji
           exchange a[j] and a[i]
    代码实现:
    复制代码
     1 void Shuffle_FisherYates(char *arr, const int len)
     2 {
     3     for(int i = len - 1; i > 0; i--)
     4     {
     5         int a = rand()%(i + 1);
     6         int temp = arr[i];
     7         arr[i] = arr[a];
     8         arr[a] =  temp;
     9     }
    10 }
    复制代码

    下面我们来证明算法3的正确性,即证明每个数字在某个位置的概率相等,都为1/n:

    对于原排列最后一个数字:很显然他在第n个位置的概率是1/n,在倒数第二个位置概率是[(n-1)/n] * [1/(n-1)] = 1/n,在倒数第k个位置的概率是[(n-1)/n] * [(n-2)/(n-1)] *...* [(n-k+1)/(n-k+2)] *[1/(n-k+1)] = 1/n

    对于原排列的其他数字也可以同上求得他们在每个位置的概率都是1/n。

    这样算法2就是明显错误的:因为算法2中第一次随机选择后,第一个数字在第一个位置的概率是1/n,后面的随机选择只能使这个概率逐渐变小


    如果我们想保留原始的排列,洗牌后的排列放到一个额外的数组,那么改用怎么样的洗牌算法呢

    算法4(正确):inside-out算法,算法的思想就是遍历原数组,把原数组中位置 i 的数据随机放到新数组的前i个位置(包括第i个)中的某一个(假设放到第k个),然后把新数组的第k个位置的数放到新数组的第 i 个位置,代码如下:

    复制代码
     1 void Shuffle_InsideOut(char *arrSrc, const int len, char *arrDest)
     2 {
     3     arrDest[0] = arrSrc[0];
     4     for(int i = 1; i < len; i++)
     5     {
     6         int k = rand()%(i + 1);
     7         arrDest[i] = arrDest[k];
     8         arrDest[k] = arrSrc[i];
     9     }
    10 }
    复制代码

    该算法空间复杂度O(n),时间复杂度O(n)

    证明算法4的正确性:原数组的第 i 个元素在新数组的前 i 个位置的概率都是:(1/i) * [i/(i+1)] * [(i+1)/(i+2)] *...* [(n-1)/n] = 1/n,(即第i次刚好随机放到了该位置,在后面的n-i 次选择中该数字不被选中)

                               原数组的第 i 个元素在新数组的 i+1 (包括i + 1)以后的位置(假设是第k个位置)的概率是:(1/k) * [k/(k+1)] * [(k+1)/(k+2)] *...* [(n-1)/n] = 1/n(即第k次刚好随机放到了该位置,在后面的n-k次选择中该数字不被选中)

    算法4还可以用于未知原始数组大小的情况下的洗牌,从代码中可以看出,没加入一张新牌,后面的计算都和牌的总数目无关,只与当前牌的数目有关


    c++ STL中有随机洗牌的函数,头文件#include<algorithm>中,调用如下random_shuffle(arr, arr+len); (其中len是数组arr的元素个数),为了统一测试,我们测试该函数时使用如下调用:

    1 void Shuffle_STL(char *arr, const int len)
    2 {
    3     random_shuffle(arr, arr+len);
    4 }

    测试一个洗牌程序的正确性:运行该洗牌程序m次,然后计算每张牌在每个位置出现的次数,这个次数应该接近m/n,其中n为牌的数目

    测试算法1~3以及STL洗牌的函数:

     View Code

    测试算法4的函数:

     View Code

    测试代码(每个算法测试100000次)

    复制代码
     1 int main()
     2 {
     3     srand((unsigned)time(NULL));
     4     char arr[10] = {'A','B','C','D','E','F','G','H','I','J'};
     5     printf("算法1:
    ");
     6     testShuffle(arr, 10, Shuffle_randomSwap, 100000);
     7     printf("算法2:
    ");
     8     testShuffle(arr, 10, Shuffle_FisherYates_change1, 100000);
     9     printf("算法3:
    ");
    10     testShuffle(arr, 10, Shuffle_FisherYates, 100000);
    11     printf("STL洗牌:
    ");
    12     testShuffle(arr, 10, Shuffle_STL, 100000);
    13     printf("算法4:
    ");
    14     testShuffle(arr, 10, Shuffle_InsideOut, 100000);
    15     return 0;
    16 }
    复制代码

    测试结果:

    算法1:主对角线上的次数明显是有问题的

    算法2:主对角线右上方第一个对角线(12798开头)数据明显有问题

  • 相关阅读:
    java空指针异常
    iOS 中strong,weak,copy,assign区别
    VisualSVN修改默认端口 443、8443 的 方法
    SVNServer 执行上下文错误: 远程主机强迫关闭了一个现有的连接。
    阿里云打开端口(涉及端口)的操作
    使用VisualSVN Server搭建SVN服务器(测试通过)
    SQL server触发器中 update insert delete 分别给写个例子被
    SQL中字符串截取函数(SUBSTRING)
    精选 SQL 脚本
    树形结构有关的SQL
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3385060.html
Copyright © 2011-2022 走看看