【发布时间】:2016-05-08 04:23:15
【问题描述】:
假设我们要从大小为n 的总集合中选择一个大小为m 的随机子集。因为可以使用来自S = {0, 1, 2, ..., (n - 1)} 的唯一索引来识别整个集合中的每个元素。该问题相当于从S中随机选择m不同的元素。
一个简单的算法将重复调用伪随机数生成器rand 以从S 生成随机数。如果之前已经生成了一个数字,请再试一次。该算法终止,直到生成m 不同的数字。该算法的最佳空间复杂度为O(1),但调用rand 的次数可能超过m 次。
我更关心时间复杂度而不是空间复杂度,如果合理的话,我很乐意用空间换时间。所以我实现了以下算法。它准确地调用了rand 次min{m, (n - m)},但代价是空间复杂度增加了O(n)。 (原码可在here找到)
template <typename Clock = std::chrono::high_resolution_clock>
auto tick_count() {
return Clock::now().time_since_epoch().count();
}
template <typename OutIt, typename RAND = std::minstd_rand,
typename Uint = typename RAND::result_type>
void random_subset(std::size_t m, std::size_t n, OutIt it, RAND&& rand =
RAND(static_cast<Uint>(tick_count()))) {
assert(n - 1 <= rand.max());
assert(m <= n);
if (m == 0) return;
auto swapped = false;
auto tmp = n - m;
if (tmp < m) {
m = tmp;
swapped = true;
}
std::vector<std::size_t> indices(n);
std::iota(indices.begin(), indices.end(), static_cast<std::size_t>(0));
auto back_it = indices.end();
for (std::size_t i = 0; i < m; ++i) {
auto idx = rand() % (n - i);
std::swap(indices[idx], *--back_it);
}
swapped ? std::copy(indices.begin(), back_it, it) :
std::copy(back_it, indices.end(), it);
}
我想知道算法是否可以在性能方面进一步改进。也欢迎对通用实现进行改进。
【问题讨论】:
-
为什么不使用
std::uniform_int_distribution之类的东西? -
@πάνταῥεῖ 因为我从
0..(n - 1)生成随机数。一个基本的 URNG 就足够了。 -
我们是否正在尝试重塑
std::experimental::sample? -
另外,我很想对
% (n - i)hack 单独投反对票。 -
如果 n_max 足够小,那么您可以做的是预先填充所有组合 C(m,n) 的向量并随机返回其中一个。每次调用算法只会调用一次随机化例程。不幸的是 n_max = 1000000 太多了。
标签: c++ algorithm random c++14 generic-programming