洗牌算法拓展(从n个数中随机m个数)

  一般洗牌算法达到的效果就是打乱序列,可以理解为从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

 

数据可能存在一定误差,基本相对准确。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值