在 O(log(n)) 时间内从 std::set 中随机选择一个元素

Posted

技术标签:

【中文标题】在 O(log(n)) 时间内从 std::set 中随机选择一个元素【英文标题】:Randomly select an element from a std::set in O(log(n)) time 【发布时间】:2018-08-21 15:50:08 【问题描述】:

有没有办法从std::set 中的std::setO(log(n)) 中随机选择一个元素 更好O(1)时间?它不需要有一个非常均匀的分布,只是一些相当随机的东西 (虽然显然甚至更好)

看着std::set::extract 似乎很有希望 因为它可能会在恒定时间内将set 分成两半,但我找不到 识别靠近根的节点的好方法,我可以使用node_type 文档 find 没有详细说明。

如果一切都失败了,可以将内容复制到std::map,并使用我认为的随机键 是O(n log(n)) 时间,这会让我摊销O(log(n)) 时间,但这不是首选的解决方案 因为在我什么都不想要的情况下需要一些开销

【问题讨论】:

您需要使用std::set吗?如果你想要随机访问,那么你应该使用提供随机访问的容器。 @NathanOliver 是的,std::set 在我的代码的其他地方提供了显着的性能优势,我宁愿不使用不同的容器 std::set 已经提供了O(ln(n)) 访问时间。如果你想要O(1),那么你应该使用std::unordered_set。见:***.com/questions/181693/… 【参考方案1】:

如果集合中的元素本身均匀分布在某个值域内,那么您可以在该域内生成一个随机值,并使用std::set::lower_bound 获取集合中包含的第一个不小于随机值。

鉴于您不需要非常均匀的分布,因此集合内元素的均匀性要求可能不是非常必要的。一个元素被选中的可能性取决于它与前一个元素的比较距离。

对于均匀分布,我认为没有比 *std::next(std::begin(s), random_index) 更好的了,它的复杂性是线性的。


对于具有均匀分布和对数渐近复杂度的良好通用解决方案,您需要std::set以外的其他数据结构。

特别是,一个不错的选择是Order statistic tree,它通过将其子树的大小添加到节点中来扩充搜索树。 OST有一个Select(i)操作,类似于数组下标操作,同样可以在索引0...N之间随机选取一个元素。

另一种选择是使用排序数组。 sorted 属性可用于保留std::set 具有的对数查找属性。

不幸的是,标准库既没有顺序统计树,也没有排序数组集。

【讨论】:

【参考方案2】:

std::set 复杂度是 o(log(n)) 所以你不能单独使用 std::set 来降低搜索复杂度。您可以使用像向量这样的索引结构来实现这一点。

此外,您无法使用此代码实现随机搜索:

 std::random_device              rd;
 std::mt19937                    gen(rd());
 std::uniform_int_distribution<> dis( 0, set::size ( ) );

然后

 set::operator [] (dis(gen));

或其他的东西

【讨论】:

以上是关于在 O(log(n)) 时间内从 std::set 中随机选择一个元素的主要内容,如果未能解决你的问题,请参考以下文章

在 O(n) 时间内从容器中移除 <number> 个元素

获得std :: set中间(中位数)的有效方法?

leetcode题解之Find the Duplicate Number

Codility峰的O(N * log(log(N)))算法?

O(n) 和 O(log(n)) 之间的区别 - 哪个更好,O(log(n)) 到底是啥?

O(n log n) 时间和 O(1) 空间复杂度与 O(n) 时间和 O(n) 空间复杂度的算法