一般洗牌算法达到的效果就是打乱序列,可以理解为从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)
n | m | random_shuffle | myshuffle |
10000 | 10000 | 0 | 0 |
100000 | 100000 | 16 | 15 |
1000000 | 1000000 | 63 | 47 |
10000000 | 10000000 | 624 | 436 |
100000000 | 100000000 | 6224 | 4332 |
1000000 | 100000 | 46 | 0 |
1000000 | 10000 | 47 | 0 |
1000000 | 1000 | 47 | 0 |
1000000 | 500000 | 62 | 15 |
1000000 | 900000 | 62 | 46 |
数据可能存在一定误差,基本相对准确。