zoukankan      html  css  js  c++  java
  • 洗牌程序的两种实现方法比较

    方法一:随机生成法

    首先,我介绍一种很常见的方法:随机生成法(我自己命名的),这方法我在扫雷游戏中随机分布雷的位置时用过(思想是一样的),该方法要点就是从头开始逐个随机生成规定区域的数字,如果新生成随机数之前已经生成过就不保存该数;如果新生成的随机数之前没有生成过就保存该数;直到生成的数字的数量达到所需的数量。

    实现代码如下:

    size_t shuffle(char s[], int n)
    {
        size_t t=0;//计算循环次数
        int c=0;
        while(c<n)
        {
            t++;
            int num = rand()%n;
            if (memchr(s,num,c) == NULL)
            {
                s[c++] = static_cast<char>(num);
            }
        }
        return t;
    }
    
    
    void printCards(char s[], int n)
    {
        for (int i=0; i<n; i++)
        {
            cout << static_cast<int>(s[i]) << " ";
        }
        cout << endl;
    }

    代码中使用了memchr函数(时间复杂度可能是O(n),没找到依据),即使是O(1),它的循环生成随机数的次数是不固定的(大于等于需要生成的个数)。

    方法二:交换位置法

    这种方法是我昨天在参加腾讯笔试考试时候想到的,今天回到学校后在寝室测试了一番,基本思路是:先初始化一串分布的数字,然后为每个位置依次生成一个与之交换的随机位置,如果生成的随机位置不是它本身就执行交换操作。

    实现代码:

    void swap(int& a, int& b)
    {
        a = a^b;
        b = a^b;
        a = a^b;
    }
    
    size_t shuffle2(int s[], int n)
    {
        size_t t=0;//计算循环次数
        for (int i=0; i<n; i++)
        {
            t++;
            s[i] = i;
        }
        for (int i=0; i<n; i++)
        {
            t++;
            int num = rand()%n;
            if (num != i)
            {
                swap(s[num],s[i]);
            }
        }
        return t;
    }
    
    void printCards2(int s[], int n)
    {
        for (int i=0; i<n; i++)
        {
            cout << s[i] << " ";
        }
        cout << endl;
    }

    比较:在时间上方法二比方法一快好多,因为交换位置的次数的最大值是限定了的(生成随机数的次数是固定的),而且省去了查找新生成数是否在已生成数中的时间。方法一中,当新生成的数在已生成的数中就需要从新生成一个随机数,从而随机生成数的次数是不固定的(有最小值)。

    测试代码:

    
    
    
    
    #include <cstdlib>
    #include <ctime>
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    const int CARDS_COUNT=54;
    
    
    int main()
    {
        srand((unsigned int)time(NULL));
        char s[CARDS_COUNT];
        int s2[CARDS_COUNT];
        size_t t1,t2;
        
        int n=2000;
        while (--n>0)
        {
            t2 = shuffle2(s2,CARDS_COUNT);
            printCards2(s2,CARDS_COUNT);
    //        cout << "方法二循环次数:" << t2 << endl;
    
            t1 = shuffle(s,CARDS_COUNT);
            printCards(s,CARDS_COUNT);
    //        cout << "方法一循环次数:" << t1 << endl;
            if (t1>t2)
            {
                cout << "方法二快" << endl;
            }
            else
            {
                cout << "方法一快" << endl;
            }
        }
        return 0;
    }

    结果:

    image

    我还是不能确定第二种方法是不是更好的,因为是自己想到的,我的验证也不是很完善,也许有什么其他的缺点(比如说随机性太弱),也没在其他书上看到过,如果网友们在哪看到过就告诉下我吧,方法一是在《c和c++代码精粹》中文版P97中找到的。

    后续补充:

    谢谢chncwang的回复,方法二不是完全随机的,完全随机的修改如下:

    size_t shuffle22(int s[], int n)
    {
        size_t t=0;//计算循环次数
        for (int i=0; i<n; i++)
        {
            t++;
            s[i] = i;
        }
        for (int i=n-1; i>0; --i)
        {
            t++;
            int num = rand()%(i+1);
            if (num != i)
            {
                swap(s[num],s[i]);
            }
        }
        return t;
    }

    因为"第1次移动后,第1个数还在第1个位置的概率是1/n,后续移动只会减少这个概率。所以这个算法不是完全随机的"。修改后的算法其实就是使用C++的STL<algorithm>库中的random_shuffle()函数的实现方法。取随机数的时候的范围每次都随着i的改变而变动,这样就不会减少之前的位置的数还是原来的数的概率了(即后续交换操作不会影响到已经交换过的位置)。

    使用标准库<algorithm>中的random_shuffle()函数实现很简单,代码如下:

    int main()
    {
        vector<int> s_stl;
        for (int i=0; i<CARDS_COUNT; ++i) s_stl.push_back(i);
        random_shuffle(s_stl.begin(),s_stl.end());
        cout << "使用C++算法库:";
        for (vector<int>::iterator it=s_stl.begin(); it!=s_stl.end(); ++it)
            cout << " " << *it;
        return 0;
    }

     

    作者:涵曦www.hanxi.cc
    出处:hanxi.cnblogs.com
    GitHub:github.com/hanxi
    Email:im.hanxi@gmail.com
    文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

    《 Skynet 游戏服务器开发实战》

  • 相关阅读:
    sleep、yield、wait、join的区别
    Java 配置全局线程池出错
    RemoteFallbackFactory扫包:No fallbackFactory instance of type class
    JAVA CopyOnWriteArrayList 说明
    Java四种线程池
    jvm总结
    CompletableFuture基本用法【转】
    Visual Studio 当前不会命中断点的问题
    安聊系统1.0发布
    libvpx编译
  • 原文地址:https://www.cnblogs.com/hanxi/p/2725047.html
Copyright © 2011-2022 走看看