【问题标题】:How to random retrieve an element from C++ hash table in O(1)如何在 O(1) 中从 C++ 哈希表中随机检索一个元素
【发布时间】:2015-03-19 07:13:27
【问题描述】:

有没有办法在 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. 在 O(1) 中从数据结构中随机检索一个元素

std::vector 可以随机访问,但是插入/删除开销很大

std::list 没有随机访问权限

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

所以我认为哈希表是最好的选择,但是随机检索一个元素将是不平凡的。

感谢您的宝贵时间。

【问题讨论】:

  • 什么意思?这 O(1) 预期的(尽管它是错误的代码,因为它错误地使用了rand,并且无论如何,rand 应该被视为弃用,以支持&lt;random&gt; header)。此外,“random access” 通常还有其他含义。
  • “随机访问”是指“选择集合中的一个随机元素吗?”
  • 在这段代码中,最坏的情况是rand()%s.size()总是返回一个大于i的数字。这意味着您将访问n 元素,所以这显然是O(n)。这可能不是你想要的,但它是你会得到的。
  • @RafaelLerm 请注意,它与i 进行比较,但增加了start。我假设 OP 实际上想要 做的是别的事情,即选择 n 个随机元素……
  • 我看到了,但是i 仍然是循环的条件,所以我认为最坏的情况仍然成立。我还没有决定OP想要什么。它要么选择 n 个随机元素,在这种情况下 O(1) 问题只是愚蠢的,要么他想得到一个随机元素。

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


【解决方案1】:

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

取决于你的目的是什么算作“随机”,以及比 O(1) 稍微高一点是否足够好。您可以在0s.bucket_count() - 1 之间选择一个随机桶“b”,如果桶为空则重复,然后在0s.bucket_size(b) - 1 之间选择列表索引li,然后std::advance(s.begin(li)) 以获取迭代器到“随机”元素,但是,考虑这种情况:

你掷三个骰子 - 然后随机选择其中一个:你得到一个随机的 1-6 值,概率是偶数,但如果你继续选择而不再次掷骰子,你只能得到最后三个骰子上的任何值骰子:从 1 到 6 的每个值的概率非常不均匀。

上面在unordered_set 中选择随机元素的方法有点像这样:如果有x 桶中的元素,那么每个桶都有被选中的机会,但是那个桶中的元素有1 / x / bucket_size() 选择的机会 - 对于任何给定的存储桶 - 可能小于或大于 1 / size()。换句话说,如果你认为散列实际上是随机的,那么各种元素在它们的位置上都有相同的机会受到青睐或受到惩罚,但是这种“偏斜”会被固定下来,直到表数据发生显着变异或表被重新散列(并且如果它通过说加倍表大小而不是更大的素数来重新散列(unordered_set 加倍的模糊记忆),那么曾经受到惩罚的值往往会在一半的时间里受到惩罚)。

上面的 big-O 效率比 O(1) 略高一点,因为:

  • 在初始探测中有一些重复以查找包含元素的存储桶,但在负载因子为 1.0 的情况下,不太可能需要多次尝试(假设哈希函数良好);其他选项可用——比如从一个空桶迭代,或者跳跃各种位移(修改为表大小)——这可能比尝试另一个完全随机的桶表现得好一点,但也可能加剧元素选择几率的差异

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

【讨论】:

    【解决方案2】:

    您不能在 O(1) 时间内从 unordered_set 中选择一个随机元素。迭代器是ForwardIterators,而不是RandomAccessIterators。您将不得不使用不同的容器。 boost::container::flat_set&lt;int&gt; 或编写您自己的,内部也有类似 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 。

    【讨论】:

    • 如果unordered_set 的哈希表重新散列,vector 中的指针将失效。如果元素数量已知,则可以避免这种情况。否则,我相信元素的副本需要同时属于两者(或一些类似“智能指针”的对象)。
    【解决方案3】:

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

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

    在下面的示例中,我使用了std::vector,然后我通过对其应用std::unique 算法来确保它只有唯一元素。然后我使用random 实用程序在 [0, vector's size - 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-27
      • 1970-01-01
      • 2013-10-28
      • 1970-01-01
      • 2013-11-09
      • 2013-06-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多