C++ 什么';从数据列表中生成随机序列的最快方法是什么?
假设我有一个数据列表:{1,2,3,4,5,6,7,8,9,10},其中n=10个元素 我想随机选择这个集合中的k个元素组成一个子列表,比如k=5 在这种情况下,我可以得到一个子列表,看起来像{9,3,5,2,7} 我可以通过:C++ 什么';从数据列表中生成随机序列的最快方法是什么?,c++,list,random,sequence,C++,List,Random,Sequence,假设我有一个数据列表:{1,2,3,4,5,6,7,8,9,10},其中n=10个元素 我想随机选择这个集合中的k个元素组成一个子列表,比如k=5 在这种情况下,我可以得到一个子列表,看起来像{9,3,5,2,7} 我可以通过: 随机确定列表中的偏移量,介于0和列表的当前大小减去1之间,将该元素添加到我的子列表中,从原始列表中删除该元素,重复此操作,直到找到所需的大小 这样做的问题是,随着原始列表的增长,偏移量和删除时间也会增加,对于任何非常大的列表(比如超过1000000个元素),执行此算法需
- 随机确定列表中的偏移量,介于0和列表的当前大小减去1之间,将该元素添加到我的子列表中,从原始列表中删除该元素,重复此操作,直到找到所需的大小
这样做的问题是,随着原始列表的增长,偏移量和删除时间也会增加,对于任何非常大的列表(比如超过1000000个元素),执行此算法需要相当长的时间
有没有更快的方法从给定数据列表生成随机序列?对于这个问题,应该将随机数生成器的实现放在一边,而是将重点放在如何在所提出的算法中使用RNG结果上
有什么想法吗
现在我用C++ STL列表
,你可以把它混洗,然后把你想要的许多元素复制到一个新的列表中。您可以通过提供第三个参数来更改生成器 它需要随机访问迭代器,因此您可以切换到
(它通常比std::vector
(可以说是更差的容器)优越得多,而且更受欢迎),或者只对某个数组进行操作。我将演示这两个方面:std::list
现在一切都处于随机顺序,只需将第一个int data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; std::random_shuffle(data, data + 10); // or std::vector data; // populate it std::random_shuffle(data.begin(), data.end());
元素视为您的子集:k
// now treat data[0] through data[k] as your random subset, or: std::vector subset(data, data + k); // or data.resize(k); // shrink vector
请注意,在另一个问题中,Jerry可能不想做你想做的事情。洗牌列表,然后选择第一个(或最后一个)k元素。如果使用O(n)算法,比如洗牌,那么整个过程就是O(n)。使用一些
然后,您可以从数组的开头查看随机元素。或者您可以通过以下方式完成此操作:- 随机确定内的偏移量 列表,介于0和当前值之间 列表的大小
- 将该元素附加到 子列表
- 重复此操作,直到子列表的长度可能足以包含正确数量的元素。例如,如果从1000000个元素中选择10个元素,那么10个子列表可能足够长。在计算必须选择的额外元素的数量时,不需要过于精确
- 现在检查子列表中的所有元素是否不同。如果没有,请删除重复项。如果您的子列表现在太短,请从主列表中再选择一些。如果没有,你就完了
我不知道这种方法的性能如何与建议的10^6个元素的随机洗牌的性能相比。一个使用输出计算器和
的最小示例。请注意,该算法将修改原始输入,因此在调用该函数之前制作一个副本是合理的std::random\u shuffle
#include <iostream> #include <algorithm> #include <vector> #include <iterator> template<class It, class OutIt> void take_random_n(It begin, It end, OutIt out, size_t n) { std::random_shuffle(begin, end); It end2 = begin; std::advance(end2, n); std::copy(begin, end2, out); } int main() { std::vector<int> a; int b[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; take_random_n(b, b + 10, std::back_inserter(a), 4); for(std::vector<int>::iterator it = a.begin(); it != a.end(); ++it) std::cout << *it << " "; }
#包括 #包括 #包括 #包括 模板 空取随机(开始、结束、结束、大小){ std::随机洗牌(开始、结束); 它end2=开始; 标准::前进(end2,n); std::复制(开始、结束2、输出); } int main(){ std::载体a; intb[]={1,2,3,4,5,6,7,8,9,10}; 随机抽取(b,b+10,标准::反向插入器(a),4); 对于(std::vector::iterator it=a.begin();it!=a.end();++it) 标准::cout 在“示例>现代方法”下查看
你不需要洗牌整个列表。O(k)(优于O(n))为列表中的每个条目分配一个随机数,然后按随机数对列表进行排序。从前n个您想要的条目中挑出。大多数答案建议对初始容器进行洗牌。如果您不想修改它,您仍然可以使用这种方法,但首先需要复制容器。(这很好,因为他把它变成了一个函数)然后会变成:template <typename InputIterator, typename Size, typename OutputIterator> void take_random_n(InputIterator first, InputIterator last, Size n, OutputIterator result) { typedef typename std::iterator_traits<InputIterator>::value_type value_type; std::vector<value_type> shufflingVec(first, last); std::random_shuffle(shufflingVec.begin(), shufflingVec.end()); std::copy(shufflingVec.begin(), shufflingVec.begin() + n, result); }
但是,如果包含的元素很重并且需要一些时间来复制,那么复制整个容器的成本可能会非常高。在这种情况下,最好是将索引列表无序排列:模板 void take_random_n(首先输入计数器,最后输入计数器, 大小n,输出计数器结果) { typedef typename std::迭代器特征::值类型值类型; std::vector shufflingVec(第一个,最后一个); std::random_shuffle(shufflingVec.begin(),shufflingVec.end()); std::copy(shufflingVec.begin(),shufflingVec.begin()+n,result); }
template <typename InputIterator, typename Size, typename OutputIterator> void take_random_n(InputIterator first, InputIterator last, Size n, OutputIterator result) { typedef typename std::iterator_traits<InputIterator>::value_type value_type; typedef typename std::iterator_traits<InputIterator>::difference_type difference_type; difference_type size = std::distance(first, last); std::vector<value_type> indexesVec( boost::counting_iterator<size_t>(0), boost::counting_iterator<size_t>(size)); // counting_iterator generates incrementing numbers. Easy to implement if you // can't use Boost std::random_shuffle(indexesVec.begin(), indexesVec.end()); for (Size i = 0 ; i < n ; ++i) { *result++ = *std::advance(first, indexesVec[i]); } } // Disclaimer: I have not tested the code above!
您会注意到,后一种解决方案的性能将根据您使用的迭代器的类型而有所不同:对于随机访问迭代器(如指针或模板 void take_random_n(首先输入计数器,最后输入计数器, 大小n,输出计数器结果) { typedef typename 标准::迭代器特征::值类型值类型; typedef typename 标准::迭代器特征::差异类型差异类型; 差异类型大小=标准::距离(第一个,最后一个); 标准::向量索引( boost::计数迭代器(0), boost::counting_迭代器(size)); //计数迭代器生成递增的数字。如果 //不能使用Boost std::random_shuffle(indexesVec.begin(),indexesVec.end()); 对于(尺寸i=0;i
),这将是正常的,但是对于其他类型的迭代器,使用vector::iterator
和大量调用std::distance
可以吗std::advance
//----------------------------------------------------------------------------- #include <cstdlib> //----------------------------------------------------------------------------- #include <iostream> #include <list> #include <iterator> #include <algorithm> //----------------------------------------------------------------------------- // random generator template< typename DiffType > struct RandomlyRandom{ DiffType operator()( DiffType i ){ return std::rand() % i; } }; //----------------------------------------------------------------------------- // we'll have two iterators: // - the first starts at the begining of the range // and moves one element at a time for n times // - the second starts at random in the middle of the range // and will move a random number of elements inside the range // // then we swap their values template< typename FwdIter, typename Fn > void random_shuffle_n( FwdIter begin, FwdIter end, Fn& Func, size_t n ){ typedef typename std::iterator_traits<FwdIter>::difference_type difference_type; FwdIter first = begin; FwdIter second = begin; difference_type dist = std::distance( begin, end ); difference_type offset = Func( dist ) % dist; difference_type index = offset; std::advance( second, offset ); // try to put some distance between first & second do{ offset = Func( dist ) % dist; index += offset; if( index >= dist ){ second = begin; index = offset = index % dist; } std::advance( second, offset ); std::swap( *first++, *second ); }while( n-- > 0 ); } //----------------------------------------------------------------------------- int main( int argc, char* argv[] ){ int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; std::list< int > lst( arr, arr + sizeof( arr ) / sizeof( arr[ 0 ] ) ); std::copy( lst.begin(), lst.end(), std::ostream_iterator< int >( std::cout, " " ) ); std::cout << std::endl; RandomlyRandom< std::list< int >::difference_type > rand; for( int i = 0; i < 100; i++ ){ random_shuffle_n( lst.begin(), lst.end(), rand, 5 ); std::copy( lst.begin(), lst.end(), std::ostream_iterator< int >( std::cout, " " ) ); std::cout << std::endl; } return 0; } //-----------------------------------------------------------------------------