我需要某种优先级队列来存储对<key, value>
。值是唯一的,但键不是。我将执行以下操作(最常见的是第一个):
我无法使用std::priority_queue
,因为它只支持删除头部。
目前,我正在使用未分类的std::list
。只需将新元素推向后面即可执行插入(O(1))。 操作2 在执行实际检索之前,使用list::sort
(O(N * logN))对列表进行排序。然而,删除是O(n),这有点贵。
是否有更好的数据结构?
答案 0 :(得分:4)
您可以撤消收集的顺序,即以<value, key>
顺序存储它们吗?
然后您可以使用std::map
O(logn)
时间进行插入O(n)
进行删除(遍历整个集合)和O(logn)
进行随机删除值(这将是所说地图的关键)。
如果你能找到基于哈希而不是树的map
实现(比如std::map
),那么时间会更好:O(1)
,O(n)
,{{1 }}
答案 1 :(得分:4)
当您需要订购时,请使用订购的容器。以后支付分拣成本毫无意义。
您当前的解决方案是:
O(1)
O(N log N)
O(N)
(在没有保留其他索引的情况下,这是最好的)只需使用std::multi_map
即可:
O(log N)
O(log N)
&lt; - 好多了不是吗?我们需要找到范围的结尾O(N)
现在,您可以使用std::map< key, std::vector<value> >
:
O(log M)
,其中M
是不同键的数量O(1)
(begin
保证按摊销时间不变)O(N)
你不能真正推动随机删除...除非你愿意在那里保留另一个索引。例如:
typedef std::vector<value_type> data_value_t;
typedef std::map<key_type, data_value_t> data_t;
typedef std::pair<data_t::iterator,size_t> index_value_t;
// where iterator gives you the right vector and size_t is an index in it
typedef std::unordered_map<value_type, index_value_t> index_t;
但保持第二个索引更新是容易出错的......并且将以牺牲其他操作为代价!例如,使用此结构,您将拥有:
O(log M)
- &gt;哈希映射中插入的复杂性为O(1)
O(N/M)
- &gt;需要对向量中的所有值进行索引,平均值为N/M
O(N/M)
- &gt;在哈希映射O(1)
中查找,解除引用O(1)
,从向量O(N/M)
中删除,因为我们需要移动向量的大约一半内容。使用list
会产生O(1)
...但可能不会更快(取决于因内存权衡而导致的元素数量)。还要记住,哈希映射的复杂性是分摊的。触发重新分配是因为您超出了加载因子,并且此特定插入将花费很长时间。
我真的会以std::map<key_type, std::vector<value_type> >
代替你。这是最好的选择。
答案 2 :(得分:1)
如果您使用的是Visual Studio,则会使用hash_multimap。我还应该补充一点,Boost有一个无序的多图,here。如果您需要有序的多图,STL multimap或已订购的多首广告STL multiset
答案 3 :(得分:0)
std :: multimap似乎就是你要搜索的内容。
它将存储按键排序的对象,允许您检索最低/最高键值(begin(),rbegin())以及具有给定键的所有对象(equal_range,lower_bound,upper_bound)。
(编辑:如果你只有少数项目,比如少于30项,你还应该测试只使用双端队列或向量的表现)
答案 4 :(得分:0)
如果我理解得很好,你的表现目标是快速(1)和(3),而(2)并不重要。在这种情况下,并且假设值是唯一的,为什么不只有std::set<value>
,并对(2)进行顺序搜索?对于(1)和(3),你有O(log n),对于(2)你有O(n)。更好的是,如果你的STL有std::hash_set
,你就会接近(1)和(3)的O(1)。
如果你需要比(2)的O(n)更好的东西,一种替代方案是拥有一组优先级队列。
答案 5 :(得分:0)
好的,所以我测试了很多选项,并最终得到了基于 Matthieu M。的想法。我目前正在使用std::map<key_type, std::list<value_type> >
,其中value_type
自身包含std::list<value_type>::iterator
,这对于删除非常有用。
删除必须检查向量是否为空,这意味着map
查询并可能调用erase
。最坏情况的复杂性是指密钥不同,O(logN)
用于插入,O(1)
用于检索,O(logN)
用于删除。与我的测试机器上的其他替代品相比,我获得了非常好的实验结果。
使用std::vector
在理论复杂性方面效率较低(当键完全相同时删除O(N)最差情况)和我一直在做的实验。