来自O(1)中unordered_set的随机元素

时间:2012-10-06 15:58:48

标签: c++ stl unordered-set

我看到有人提到可以在O(1)时间内从unordered_set中获取随机元素。我试图这样做:

std::unordered_set<TestObject*> test_set;

//fill with data

size_t index = rand() % test_set.size();
const TestObject* test = *(test_set.begin() + index);

但是,unordered_set迭代器不支持带整数的+。 begin可以给出一个size_t参数,但它是一个桶而不是一个元素的索引。随机选择一个桶然后随机挑选一个元素将导致非常不平衡的随机分布。

适当的O(1)随机访问的秘诀是什么?如果重要,那就是在VC ++ 2010中。

4 个答案:

答案 0 :(得分:8)

我认为你误解了“随机访问”的含义,因为它被用于你所指的那些情况。

“随机访问”与随机性无关。它意味着“随机”访问元素,即访问容器中任何位置的任何元素。直接访问元素(例如使用std::vector::operator[])是随机访问,但迭代容器不是。

将此与RAM进行比较,RAM是“随机存取存储器”的缩写。

答案 1 :(得分:5)

std::unordered_set不提供随机访问迭代器。我想这是stl设计师的选择,为stl实现者提供更多的自由......底层结构必须支持O(1)插入和删除,但不必支持随机访问。例如,您可以将符合stl的unordered_set编码为双向链表,即使无法为这样的底层容器编写随机访问迭代器。

即使第一个元素是随机的,因此获取一个完全随机的元素是不可能的,因为元素在底层容器中通过散列排序的方式是确定性的......并且在我正在使用的算法类型中,使用第一个元素会严重扭曲结果。

我可以想到一个&#34; hack&#34;,如果你可以在O(1)中构建一个随机的value_type元素......这就是这个想法:

  1. 检查无序集合是否为空(如果是,则没有希望)
  2. 生成随机value_type元素
  3. 如果已经在无序集中返回,则将其插入
  4. 在此元素上获取迭代器it
  5. 将随机元素设为*(it++)(如果*it是获取第一个元素的最后一个元素)
  6. 删除您插入的元素并返回(5)
  7. 中的值

    所有这些操作都是O(1)。你可以实现我给出的伪代码并很容易地模板化。

    注意:非常奇怪的第五步也很重要...因为例如,如果你将随机元素作为it++(如果it--是最后一个迭代器,那么it)然后第一个元素可能比其他元素少两倍(不是微不足道的,但想想它......)。如果你不在乎倾斜你的发行版,那么你就可以获得前面的元素。

答案 2 :(得分:3)

std::unordered_set在数组意义上没有O(1)随机访问。可以基于键访问O(1)中的元素,但是找不到第k个元素。

尽管如此,这是一种从std::unordered_map(或如果密钥具有可变字段,则使用std::unordered_set)获得具有均匀分布的随机元素的方法。我在回答SO Data Structure(s) Allowing For Alteration Through Iteration and Random Selection From Subset (C++)问题时提出了类似的技术。

这个想法是在std::unordred_set中用可变的索引值补充unordered_set中的每个条目。向量的大小是unordered_set的大小。每次将新元素插入unordered_set时,指向该元素的指针就会push_back被插入向量中。每次从unodrered_set中删除一个元素时,向量中的相应条目都位于O(1)中,并与向量的back()元素交换。先前back()元素的索引已修改,现在指向其在向量中的新位置。最后,旧条目是向量中的pop_back()-ed

此向量精确指向unordered_set中的所有元素。从组合结构中以均匀分布选择随机元素需要O(1)。需要O(1)才能向组合结构中添加或删除元素。

注意:只要元素存在,就可以确保指向元素的指针(与迭代器不同)。

这是下面的样子: three elements in the set

要删除元素c:

  1. 交换元素c_index和a_index并修复指向它们的指针:
  2. pop_back最后一个元素,它是向量中的element_c。
  3. unordred_set中删除c。

随机化是微不足道的-只需从向量中随机选择一个元素即可。

答案 3 :(得分:0)

我使用buck_count()和cbegin(n)方法编写了一个解决方案,随机选择一个存储桶,然后在存储桶中随机选择一个元素。

两个问题:   - 这不是恒定的时间(更糟糕的情况是有很多空桶和一个桶中的许多元素)   - 概率分布偏斜

我认为随机查看元素的唯一方法是维护一个提供随机访问迭代器的单独容器。

#include <random>
#include <iostream>
#include <unordered_set>
#include <unordered_map>
#include <cassert>

using namespace std;

ranlux24_base randomEngine(5);

int rand_int(int from, int to)
{
    assert(from <= to);

    return uniform_int_distribution<int>(from, to)(randomEngine);
}

int random_peek(const unordered_set<int> & container)
{
    assert(container.size() > 0);

    auto b_count = container.bucket_count();
    auto b_idx = rand_int(0, b_count - 1);
    size_t b_size = 0;

    for (int i = 0; i < b_count; ++i)
    {
        b_size = container.bucket_size(b_idx);
        if (b_size > 0)
            break;

        b_idx = (b_idx + 1) % b_count;
    }

    auto idx = rand_int(0, b_size - 1);

    auto it = container.cbegin(b_idx);

    for (int i = 0; i < idx; ++i)
    {
        it++;
    }

    return *it;
}

int main()
{
    unordered_set<int> set;

    for (int i = 0; i < 1000; ++i)
    {
        set.insert(rand_int(0, 100000));
    }

    unordered_map<int,int> distribution;

    const int N = 1000000;
    for (int i = 0; i < N; ++i)
    {
        int n = random_peek(set);
        distribution[n]++;
    }

    int min = N;
    int max = 0;

    for (auto & [n,count]: distribution)
    {
        if (count > max)
            max = count;
        if (count < min)
            min = count;
    }

    cout << "Max=" << max << ", Min=" << min << "\n";
    return 0;
}