std::unordered_set
在数组的意义上没有 O(1) 随机访问。可以根据键在 O(1) 中访问元素,但不可能找到第 k 个元素。
尽管如此,这是一种从std::unordered_map
(或者std::unordered_set
如果键具有可变字段)获得具有均匀分布的随机元素的方法。我在对 SO question Data Structure(s) Allow For Alteration Through Iteration and Random Selection From Subset (C++)的回答中提出了类似的技术。
这个想法是std::unordered_set
用一个可变索引值将每个条目补充到指向unordered_set
. 向量的大小是 的大小unordered_set
。每次将新元素插入到 中时unordered_set
,指向该元素的指针都会被push_back
-ed 到向量中。每次从 unordered_set 中删除一个元素时,向量中的相应条目位于 O(1) 中,并与back()
向量的元素交换。先前元素的索引back()
被修改,现在指向它在向量中的新位置。最后,旧条目pop_back()-ed
来自向量。
这个向量正好指向unordered_set
. 从均匀分布的组合结构中选择一个随机元素需要 O(1)。在组合结构中添加或删除元素需要 O(1)。
注意:只要元素存在,指向元素的指针(与迭代器不同)就保证保持有效。
这应该是这样的:
对于擦除元素 c:
- 交换元素 c_index 和 a_index 并修复指向它们的指针:
- pop_back 最后一个元素,也就是向量中的 element_c。
unordered_set
从.中删除 c
随机化是微不足道的——只需从向量中随机选择一个元素。
编辑:这是一个部分代码,可以从 unordered_set 返回均匀分布的随机元素。我不得不做一些与上面的解释稍有不同的事情,因为 unordered_set 中没有可靠的索引(或迭代器)。无法将迭代器保存到 unordered_set 中的原因是它的元素不时被重新散列,从而使过程中的所有迭代器无效。因此,这个解决方案不是使用稳定的索引,而是简单地使用指向永远不会重新分配的对象的指针:
#include <unordered_set>
#include <functional>
#include <vector>
#include <memory>
#include <random>
template <class T>
class RandomUnorderedSet
{
private:
struct Entry {
Entry(const T & data_in, unsigned index_in_vector_in)
: data(data_in), index_in_vector(index_in_vector_in)
{}
T data;
unsigned index_in_vector;
};
struct PtrEntryHash {
auto operator()(const std::unique_ptr<Entry> & entry) const
{
return std::hash<T>()(entry->data);
}
};
struct PtrEntryEqual {
bool operator()(const std::unique_ptr<Entry> & a,
const std::unique_ptr<Entry> & b ) const
{
return a->data == b->data;
}
};
public:
bool insert(const T & element)
{
auto entry_ptr = std::make_unique<Entry>(element, m_entry_vector.size());
if (m_entry_set.count(entry_ptr) > 0)
return false;
m_entry_vector.push_back(entry_ptr.get());
try {
m_entry_set.insert(std::move(entry_ptr));
} catch(...) {
m_entry_vector.pop_back();
throw;
}
return true;
}
// Return the number of elements removed
int erase(const T & element)
{
auto it = m_entry_set.find(element);
if (it == m_entry_set.end())
return 0;
auto swap_with = it->index_in_vector;
if (swap_with < m_entry_vector.size() - 1) {
m_entry_vector.back()->index_in_vector = swap_with;
m_entry_vector[swap_with] = m_entry_vector.back();
}
m_entry_set.erase(it);
m_entry_vector.pop_back();
return 1;
}
template <typename RandomGenerator>
const T & random_element(RandomGenerator & r)
{
std::uniform_int_distribution<> dis(0, m_entry_vector.size() - 1);
return m_entry_vector[dis(r)]->data;
}
private:
std::unordered_set<std::unique_ptr<Entry>, PtrEntryHash, PtrEntryEqual>
m_entry_set;
std::vector<Entry*> m_entry_vector;
};
笔记:
- 这个实现只是一个框架,可能会添加额外的操作。
- 如果这是一个库类,那么最好把它做成一个合适的容器,有一个迭代器类型,它隐藏了实现细节,有
begin()
和end()
调用,还有一个更好的返回类型insert()
。