如何从O(1)中的C ++哈希表中随机检索一个元素

时间:2015-01-20 21:54:29

标签: c++ hashtable unordered-map random-access unordered-set

有没有办法在O(1)平均时间内从C ++ unordered_set 中随机检索一个元素?而不是做

std::unordered_set<int> s;
// initialize s
auto start = s.begin();
for (int i = 0; i < rand()%s.size()-1; ++i, ++start) {}
int randomNumber = *start;

更新

我需要争取帖子,所以我添加了我需要上述功能的原因。

我正在玩实施迷宫发生器。不知何故,我需要一个支持的数据结构:

  1. 在O(1)
  2. 中插入/删除
  3. 随机从O(1)
  4. 中的数据结构中检索元素

    std :: vector 具有随机访问权限,但插入/删除费用很高

    std :: list 没有随机访问

    std :: set 支持O(logN)随机访问和O(logN)插入/删除,这很棒,但我的初始化是一个排序的序列,很容易打破它的平衡

    所以我认为哈希表是最好的选择,但随机检索一个元素将是非常重要的。

    感谢您的时间。

3 个答案:

答案 0 :(得分:2)

您无法在O(1)时间内从unordered_set中选择随机元素。迭代器是ForwardIterator s,而不是RandomAccessIterator s。您必须使用不同的容器。要么boost::container::flat_set<int>,要么自己编写内容类似vector的内容:

template <typename T>
class set_with_random_access
{
    std::vector<T*> vec;
    std::unordered_set<T> set;
};

我们为其提供保持这些功能的功能,例如插入:

void insert(const T& value) {
    auto pr = set.insert(value);
    if (pr.second) {
        vec.push_back(&*pr.first);
    }
}

随机性:

template <typename GEN>
T& random(GEN& gen) {
    std::uniform_int_distribution<size_t> dist(0, vec.size() - 1);
    return *vec[dist(gen)];
}
坦率地说,这是很多工作,所以可能会使用boost。

答案 1 :(得分:2)

  

在O(1)平均时间内从C ++ unordered_set中随机检索元素的方法吗?

取决于&#34;随机&#34;为了你的目的,是否是一个小于O(1)的小微笑是足够好的。你可以选择一个随机的桶#34; b&#34;在0s.bucket_count() - 1之间,如果存储桶为空,则重复,然后在li0之间重复列表索引s.bucket_size(b) - 1,然后{{1}获取一个&#34;随机的迭代器&#34;但是,请考虑这种情况:

  

你滚动三个骰子 - 然后随机选择其中一个:你得到一个随机的1-6值,偶数概率,但如果你继续采摘而不再滚动你只能得到任何价值最终三个骰子:从1到6的每个值的概率严重不均匀。

std::advance(s.begin(li))中选择随机元素的上述方法有点像:如果有unordered_set桶有元素,那么每个桶甚至有机会被选中,但元素在该桶中有x个选择的机会 - 对于任何给定的桶 - 可能小于或大于1 / x / bucket_size()。换句话说,如果你认为散列是有效随机的,那么各种元素在它们的位置上都有相同的机会受到青睐或受到惩罚,但是这种情况有“歪斜”。然后设置它直到表格数据显着变异或表格重新出现(并且如果它通过加倍表格大小而不是更大的素数而重新表达(模糊的记忆{{{ 1}}双打),然后一次惩罚的值将倾向于在一半的时间内受到惩罚)。

以上的大O效率是O(1)以上的微小因素,因为:

  • 在初始探测中有一些重复,要找到一个带元素的存储桶,但加载因子为1.0,它不太可能需要多次尝试(给定一个好的散列函数) );其他选项是可用的 - 比如从空桶中迭代,或者通过各种位移跳跃(修改为表格大小) - 这可能比尝试另一个完全随机的桶更好一点,但也可能加剧元素选择的可能性差异

  • 在任何给定存储桶中碰撞的元素中存在线性迭代,但由于默认负载因子为1.0,因此很少会发生多次碰撞,并且越来越少见还有更多。

答案 2 :(得分:1)

std::unordered_set中挑选一个随机元素是一个坏主意。这是因为std::unordered_set不支持随机访问,因此没有下标运算符(即operator[])。

我坚信您需要的是std::vector并结合std::unique以满足元素的唯一性。

在下面的示例中,我使用std::vector,然后通过在其上应用std::unique算法确保它只有唯一的元素。然后我使用random实用程序来生成[0,向量的大小 - 1]中的随机索引:

std::vector<int> v{1, 2, 8, 3, 5, 4, 5, 6, 7, 7, 9, 9, 19, 19};
v.erase(std::unique(v.begin(), v.end()), v.end());

std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0, v.size() - 1);

std::cout << "Random number from vector: " << v[distribution(generator)] << std::endl;

LIVE DEMO