优先级队列的变体

时间:2010-04-01 14:36:03

标签: c++ priority-queue data-structures

我需要某种优先级队列来存储对<key, value>。值是唯一的,但键不是。我将执行以下操作(最常见的是第一个):

  1. 随机插入;
  2. 使用最少的密钥检索(并删除)所有元素。
  3. 随机删除(按值);
  4. 我无法使用std::priority_queue,因为它只支持删除头部。

    目前,我正在使用未分类的std::list。只需将新元素推向后面即可执行插入(O(1))。 操作2 在执行实际检索之前,使用list::sort(O(N * logN))对列表进行排序。然而,删除是O(n),这有点贵。

    是否有更好的数据结构?

6 个答案:

答案 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)最差情况)和我一直在做的实验。