假设我有一个数据列表:{1,2,3,4,5,6,7,8,9,10},其中n = 10个元素
我想随机选择这个集合中的k个元素来形成一个子列表,比如k = 5.
在这种情况下,我最终会得到一个看起来像{9,3,5,2,7}的子列表
我可以通过以下方式实现这一目标:
问题在于,随着原始列表的增长,偏移量和删除时间也会增长,对于任何显着大的列表(例如超过1,000,000个元素),执行此算法需要相当长的时间。
是否有更快的方法从给定数据列表生成随机序列?应该为这个问题留出随机数发生器的实现,而是关注如何在提出的算法中使用RNG结果。
有什么想法吗?
现在我正在使用C ++ STL列表
答案 0 :(得分:9)
我会使用random_shuffle
。您可以通过提供第三个参数来更改生成器。
它需要随机访问迭代器,因此您可以切换到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
元素视为您的子集:
{{1}}
请注意,在另一个问题中,Jerry shares an excellent way做了你想做的事。
答案 1 :(得分:4)
http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
查看示例>现代方法
您无需随机播放整个列表。 O(k)(优于O(n))
答案 2 :(得分:2)
使用OutputIterators和std::random_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 << " ";
}
答案 3 :(得分:1)
随机播放列表,然后选择第一个(或最后一个) k 元素。如果你使用像Fisher-Yates shuffle这样的O(n)算法,那么整个过程就是O(n)。
答案 4 :(得分:1)
或者你可以通过以下方式实现这一目标:
我不确定您为什么要从主列表中删除所选元素,但如果这是必不可少的,您可以在构建子列表后执行此操作。
我还不知道这种方法的性能如何与10 ^ 6元素列表中建议的random_shuffle的性能相比。
答案 5 :(得分:0)
您可以使用std::random_shuffle对其进行随机播放,然后将所需的第一个元素复制到新列表中。
答案 6 :(得分:0)
使用某些algorithm随机播放阵列 然后你可以从数组的开头偷看随机元素。
答案 7 :(得分:0)
为列表中的每个条目分配一个随机数,然后按随机数对列表进行排序。选择你想要的前n个条目。
答案 8 :(得分:0)
大多数答案都建议将最初的容器洗牌。如果您不希望它被修改,您仍然可以使用此方法,但首先需要复制容器。 The solution of @pmr(这很好,因为他把它变成了一个函数)然后会变成:
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);
}
但是,如果包含的元素很重并且需要一些时间来复制,那么复制整个容器会非常昂贵。在这种情况下,您可以更好地改组索引列表:
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!
您会注意到后一种解决方案的执行方式会有很大不同,具体取决于您使用的迭代器类型:使用随机访问迭代器(如指针或vector<T>::iterator
),它可以,但是使用其他类型的迭代器,std::distance
的使用以及对std::advance
的大量调用都可能导致相当大的开销。
答案 9 :(得分:0)
我的2美分(仅使用stl和最需要前向迭代器):
//-----------------------------------------------------------------------------
#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;
}
//-----------------------------------------------------------------------------