从数据列表生成随机序列的最快方法是什么?

时间:2010-07-22 16:11:18

标签: 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之前
  • 将该元素附加到我的子列表
  • 从原始列表中删除该元素< / li>
  • 重复直到找到所需的大小

问题在于,随着原始列表的增长,偏移量和删除时间也会增长,对于任何显着大的列表(例如超过1,000,000个元素),执行此算法需要相当长的时间。

是否有更快的方法从给定数据列表生成随机序列?应该为这个问题留出随机数发生器的实现,而是关注如何在提出的算法中使用RNG结果。

有什么想法吗?

现在我正在使用C ++ STL列表

10 个答案:

答案 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

查看示例&gt;现代方法

您无需随机播放整个列表。 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)

或者你可以通过以下方式实现这一目标:

  • 随机确定其中的偏移量 列表,介于0和当前之间 列表的大小。
  • 将该元素添加到您的 子列表。
  • 重复直到子列表可能足够长以包含正确数量的元素。例如,如果您从1,000,000个元素中选择10个元素,则10的子列表可能足够长。在计算必须选择的额外元素数量时,您不需要超精确
  • 现在检查子列表中的所有元素是否不同。如果没有,请删除重复项。如果您的子列表现在太短,请从主列表中选择更多。如果没有,你就完成了。

我不确定您为什么要从主列表中删除所选元素,但如果这是必不可少的,您可以在构建子列表后执行此操作。

我还不知道这种方法的性能如何与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;
}
//-----------------------------------------------------------------------------