我有一个包含 n
个元素的向量。我需要从向量中随机选择一个不重复的子集,该子集包含 m
个元素。最有效的方法是什么?在我的代码中需要执行数千次。
我想到的解决方案是使用 rand()
生成一个介于 0
和 n
之间的随机数 k
。然后选择向量中的第 k
个元素,并将其插入到一个 std::set
中。重复这个步骤直到集合的大小等于 m
。我现在可以确信,集合包含了从 n
个元素的集合中随机选择的 m
个唯一元素。
还有哪些可能的解决方案?
谢谢。
你想要一个 Fisher-Yates shuffle(在 M 次迭代后停止):
template<class BidiIter >
BidiIter random_unique(BidiIter begin, BidiIter end, size_t num_random) {
size_t left = std::distance(begin, end);
while (num_random--) {
BidiIter r = begin;
std::advance(r, rand()%left);
std::swap(*begin, *r);
++begin;
--left;
}
return begin;
}
std::random_shuffle
快得多,并且即使N==M
,速度也应该基本相同。std::random_shuffle
版本。我很好奇STL为什么没有重载这个版本。顺便说一句,谢谢:D。 - Rick您可以采用的一种方法是创建一个向量的所有索引列表,对它们进行洗牌,然后取前n
个作为所选对象的索引:
struct rangegenerator {
rangegenerator(int init) : start(init) { }
int operator()() {
return start++;
}
int start;
};
vector<T> numbers; // this is filled somewhere else
vector<int> indices(numbers.size());
generate(begin(indices), end(indices), rangegenerator(0));
random_shuffle(begin(indices), end(indices));
// then take the first n elements of indices and use them as indices into numbers
m
远小于 n
时,这种方法效率非常低下。对于所有 m
(其中 m
小于 n
),很容易想出比这种方法更快的答案。 - Mooing Duckstd::ranges::sample()
:#include <cassert>
#include <iostream>
#include <random>
#include <vector>
void Test() {
std::mt19937_64 random_engine{std::random_device{}()};
std::vector<int> in{{1, 2, 3, 4, 5}};
std::size_t m{2};
std::vector<int> out{};
std::ranges::sample(in, std::back_inserter(out), m, random_engine);
assert(out.size() == m);
for (const int &elem : out) {
std::cout << elem << std::endl;
}
}
std::random_shuffle()
,然后取出前m
个元素,怎么样? - jrokm
远小于n
时,这种方法非常低效。 - Mooing Duckstd::random_shuffle()
只是一个完全置换,它还使用了Fisher-Yates shuffle。请参阅cppreference中的可能实现的第一个版本。对于那些想要理解被接受的答案以及std::random_shuffle()
的人来说,这值得一读。 - Rickm
个元素后停止,而是对整个数据集进行操作,这是一种巨大的时间浪费。这就是std::sort
和std::partial_sort
之间的区别。 - Mooing Duck