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

How to random retrieve an element from C++ hash table in O(1)

本文关键字:哈希表 随机 检索 元素 C++      更新时间:2023-10-16

有没有办法从 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) 时间内从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)];
}

坦率地说,这是很多工作,所以可能会使用助推器。

一种从 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,因此很少发生超过几次碰撞的情况,并且越来越罕见地发生更多碰撞的情况。

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

我坚信,您需要的是结合std::uniquestd::vector,以满足元素的独特性。

在下面的示例中,我使用了一个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;

现场演示