C++ 什么';从数据列表中生成随机序列的最快方法是什么?

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个元素),执行此算法需

假设我有一个数据列表:{1,2,3,4,5,6,7,8,9,10},其中n=10个元素

我想随机选择这个集合中的k个元素组成一个子列表,比如k=5

在这种情况下,我可以得到一个子列表,看起来像{9,3,5,2,7}

我可以通过:
  • 随机确定列表中的偏移量,介于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;
    }
    //-----------------------------------------------------------------------------