洗牌程序的兩種實現方法比較


方法一:隨機生成法

首先,我介紹一種很常見的方法:隨機生成法(我自己命名的),這方法我在掃雷游戲中隨機分布雷的位置時用過(思想是一樣的),該方法要點就是從頭開始逐個隨機生成規定區域的數字,如果新生成隨機數之前已經生成過就不保存該數;如果新生成的隨機數之前沒有生成過就保存該數;直到生成的數字的數量達到所需的數量。

實現代碼如下:

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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM