洗牌演算法擴充(從n個數中隨機m個數)

歌昔發表於2020-11-13

  一般洗牌演算法達到的效果就是打亂序列,可以理解為從n個數中隨機m個數。

當n==m的時候,就是全打亂,m<n的時候,有兩種做法。

①全打亂,然後取前m項。

②每次從n中取出一個數,然後從n中刪除這個數,重複m次。

 

全打亂(n等於m)

1、random_shuffle

  一般洗牌就是全打亂,這個時候可以直接用stl的 random_shuffle 函式。原始碼如下:

template <class RandomAccessIterator>                                             
inline void random_shuffle(RandomAccessIterator first, RandomAccessIterator last) { 
    if(first != last)                                                             
        for(RandomAccessIterator i = first + 1; i != last; ++i)                   
            iter_swap(i, first + (rand() % ((i - first) + 1)));                   
} 

random_shuffle 的基本思想是這樣的:把第i個元素和前i個隨機一個元素進行對調,執行n次,達到全隨機的效果。

(如果原數列是有序數列,如1,2,3,4....50,進行5次對調操作之後,前5項已經隨機,後45項依舊不變,就不是一個我想要的隨機的效果,不太符合例如50個球裡取5個球的場景,所以一般還是要全部隨機後才具有完整隨機性)

2、Fisher-Yates Shuffle演算法


void Fisher_Yates_Shuffle(vector<int>& arr,vector<int>& res)
{
     srand((unsigned)time(NULL));
     int k;
     int len = arr.size();
     for (int i=0;i<len;++i)
     {
     	k=rand()%arr.size();
     	res.push_back(arr[k]);
     	arr.erase(arr.begin()+k);
     }
}

Fisher-Yates 洗牌演算法的基本思想是:從n中取出一個數,然後從n中刪除這個數,重複n次,達到全隨機的效果。

這部分程式碼是我從別的博主那拷貝過來的,我之前也是拿來就用,但是當n數值比較大的時候,這個程式碼效率就比較低了。

倒不是基本思想的問題,問題在於這份程式碼vector大量使用了刪除,我們知道vector是一塊連續的記憶體,中間刪除操作,需要移動後面的元素,導致效率低下。

 

半打亂(n大於m)

1、random_shuffle改

直接random_shuffle,然後取前m項倒也可以,效率不低。但是存在效率浪費。

void randVec(std::vector<int> vecAll, int m, vector<int> &vecRet)
{
    random_shuffle(vecAll.begin(),vecAll.end());
    vecRet.insert(vecRet.begin(), vecAll.begin(), vecAll.begin()+m);
}

 

2、Fisher-Yates Shuffle演算法

void randVecMine(std::vector<int> vecAll, int m, vector<int> &vecRet)
{
    srand(time(NULL));
    int k = 0;
    for (int i = 0; i < m; ++i) {
        if (vecAll.size() > 0) {
            k = i + rand() % (vecAll.size()-i);
            int tmp = vecAll[k];
            vecAll[k] = vecAll[i];
            vecAll[i] = tmp;
        }
    }
    vecRet.insert(vecRet.begin(), vecAll.begin(), vecAll.begin()+m);
}

這份程式碼相較於全打亂的Fisher-Yates Shuffle演算法程式碼,主要是修改了vector刪除元素部分,第i次隨機的時候,是從後面n-i+1個數中隨機取出。取出m個數隨機m次即可。沒有效率浪費。

 

效率比拼測試

對於random_shuffle和我寫的Fisher-Yates實現,我設計了一個效率測試。計算不同引數對n、m情況下,得出隨機結果需要的時間對比。以下是測試程式碼。

#include <iostream>
#include <vector>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;

#include<stdio.h>
#include <windows.h>

void randVec(std::vector<int> vecAll, int m, vector<int> &vecRet)
{
    DWORD start, end;
    start = GetTickCount();

    random_shuffle(vecAll.begin(),vecAll.end());
    vecRet.insert(vecRet.begin(), vecAll.begin(), vecAll.begin()+m);

    end = GetTickCount()-start;
    cout<<"random_shuffle time="<<end<<endl;
}

void randVecMine(std::vector<int> vecAll, int m, vector<int> &vecRet)
{
    DWORD start, end;
    start = GetTickCount();

    srand(time(NULL));
    int k = 0;
    for (int i = 0; i < m; ++i) {
        if (vecAll.size() > 0) {
            k = i + rand() % (vecAll.size()-i);
            int tmp = vecAll[k];
            vecAll[k] = vecAll[i];
            vecAll[i] = tmp;
        }
    }
    vecRet.insert(vecRet.begin(), vecAll.begin(), vecAll.begin()+m);

    end = GetTickCount()-start;
    cout<<"myrand time="<<end<<endl;
}

int main()
{
    int runindex = 0;
    while(runindex<10)
    {
        runindex++;



        vector<int>vec_all;
        int s;
        cin>>s;
        for(int i=0;i<s;++i)
        {
            vec_all.push_back(i+1);
        }

        vector<int>vec_part;

        int len;
        cin>>len;

        vec_part.clear();
        randVec(vec_all,len,vec_part);

        vec_part.clear();
        randVecMine(vec_all,len,vec_part);
        cout<<endl;
    }
}

 

輸入的n、m值分別是:

10000       10000
100000      100000
1000000     1000000
10000000    10000000
100000000   100000000
1000000     100000
1000000     10000
1000000     1000
1000000     500000
1000000     900000

 

結果截圖:

資料整理(單位:ms)

nmrandom_shufflemyshuffle
100001000000
1000001000001615
100000010000006347
1000000010000000624436
10000000010000000062244332
1000000100000460
100000010000470
10000001000470
10000005000006215
10000009000006246

 

資料可能存在一定誤差,基本相對準確。

 

相關文章