【问题标题】:A variation of priority queue优先队列的一种变体
【发布时间】:2010-04-01 14:36:03
【问题描述】:

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

  1. 随机插入;
  2. 检索(并删除)所有具有最少键的元素。
  3. 随机删除(按值);

我不能使用std::priority_queue,因为它只支持去除头部。

目前,我使用的是未排序的std::list插入是通过将新元素推到后面来执行的 (O(1))。 操作 2 在执行实际检索之前使用 list::sort (O(N*logN)) 对列表进行排序。 移除不过是O(n),有点贵。

有更好的数据结构的想法吗?

【问题讨论】:

  • 一个vector而不是一个list,除非你真的有很多元素。
  • 您希望保证value 的唯一性还是有其他需要注意的地方?
  • 不需要保证value的唯一性。 ps.:我看到你是法国人,因为“?”之前的空格。 :-)

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


【解决方案1】:

当您需要订购时,请使用订购的容器。以后再支付排序费用是没有意义的。

您当前的解决方案是:

  • 插入O(1)
  • 检索O(N log N)
  • 删除O(N)(在不保留另一个索引的情况下尽可能好)

只需使用std::multi_map,您就可以拥有:

  • 插入O(log N)
  • 检索O(log N)
  • 删除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) --> 哈希映射中插入的复杂度是O(1)
  • 检索O(N/M) --> 需要对向量中的所有值进行de索引,平均有N/M
  • 删除O(N/M) --> 在哈希映射中找到O(1),取消引用O(1),从向量中删除O(N/M),因为我们需要移动大约一半的向量内容。使用 list 会产生 O(1)... 但可能不会更快(取决于内存权衡的元素数量)。

还请记住,哈希映射复杂性是摊销的。触发重新分配,因为您超出了负载因子,并且此特定插入将花费很长时间。

我真的会用std::map&lt;key_type, std::vector&lt;value_type&gt; &gt; 代替你。这是最划算的。

【讨论】:

  • boost 具有多索引容器。因此,如果您要走那条路,请使用其他人已经调试过的代码。
  • 不幸的是,我不知道用MultiIndex 容器获得最终结构的方法。我的意思是,您可以要求 multiset 键和 hash_map 具有唯一约束的值,但这不是我设计的最终设计。不过可能已经足够好了。
【解决方案2】:

你可以颠倒集合的顺序,即以&lt;value, key&gt;的顺序存储它们吗?

然后你可以只使用 std::mapO(logn) 插入时间 O(n) 删除(遍历整个集合)和 O(logn) 随机删除值(这将是所述地图的关键)。

如果你能找到基于哈希而不是树的map 实现(如std::map),那么时间会更好:O(1)O(n)O(1)

【讨论】:

  • +1。或者非标准容器hash_map/很快成为标准std::unordered_map
  • 它需要按键排序和检索,所以我不确定这是否可行。
  • @BillyONeal 是的,hashmap 会产生更好的时间O(1)O(n)O(1)。 @MarkB 他没有在任何地方说明,那你为什么这么认为?
  • 实际上,我更喜欢你的回答——标准容器应该用在非标准容器上,除非你有非常很好的理由不这样做,因为标准容器就是这样 - - 标准。并且散列容器不提供 O(1) 最坏情况下的性能——但它们平均提供。对于某些应用程序,摊销分析是不允许的做法。
【解决方案3】:

如果您使用的是 Visual Studio,他们有 hash_multimap。我还应该补充一点,Boost 有一个无序的多图,here。如果您需要有序多图,STL multimap 或有序多集 STL multiset

【讨论】:

  • 我认为标准容器在大多数情况下应该是默认选项,因为它是标准容器。
  • pfft,标准容器。如果人们使用这些程序会更少崩溃。然后像我这样的人就不必向老板解释为什么一段不相关的代码会影响新功能的生产,而我们只是“幸运”了它从未发生过。然后,我们永远不必聘请高价顾问来编写“更好”的容器,直到它们崩溃才崩溃。你在这里看到了你试图破坏的生命周期!?
  • @Helltone multimap 按顺序保存事物,但每个键可以采用多个值。插入时间为 O(log),而检索时间可达 O(N)。我认为你应该使用多地图。
【解决方案4】:

std::multimap 似乎是您正在搜索的内容。

它将存储按键排序的对象,允许您检索最低/最高键值(begin()、rbegin())和具有给定键(equal_range、lower_bound、upper_bound)的所有对象。

(编辑:如果您只有几个项目,比如少于 30 个,您还应该测试仅使用双端队列或向量的性能)

【讨论】:

    【解决方案5】:

    如果我理解得很好,你的性能目标是快速(1)和(3),而(2)并不那么重要。在这种情况下,鉴于值是唯一的,为什么不只使用std::set&lt;value&gt;,然后按顺序搜索 (2)? (1) 和 (3) 的 O(log n) 和 (2) 的 O(n)。更好的是,如果您的 STL 有 std::hash_set,那么 (1) 和 (3) 的 O(1) 将接近于 O(1)。

    如果对于 (2),您需要比 O(n) 更好的东西,那么另一种选择是拥有一组优先级队列。

    【讨论】:

    • 我说操作是最常见的首先。我最初的解决方案比你的更好,因为它的插入 O(1)。
    • 我认为你不能在不放弃 (1) 的情况下加快其他操作,这里没有魔法。无论如何,如果你有一个 std::hash_set 实现,插入非常接近 O(1)
    • 你的解决方案Helltone没有计时,不知道是不是更好。值得注意的是,您忘记考虑速度的内存方面,例如缓存和堆使用。除非你计时,否则你不能确定它是否更快。对于较小的 n 值,较大的 O(1)O(log(n)) 差。
    【解决方案6】:

    好的,所以我已经测试了许多选项,最终得到了基于 Matthieu M. 想法的东西。我目前正在使用std::map&lt;key_type, std::list&lt;value_type&gt; &gt;,其中value_type 自身包含std::list&lt;value_type&gt;::iterator,这对于删除很有用。

    删除必须检查向量是否为空,这意味着map 查询和可能对erase 的调用。最坏情况的复杂性是键不同时,O(logN) 用于插入,O(1) 用于检索,O(logN) 用于删除。与我的测试机器上的其他替代方案相比,我得到了非常好的实验结果。

    在理论上的复杂性(当密钥相同时删除 O(N) 最坏情况)和我一直在做的实验方面,使用 std::vector 效率较低。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-20
      • 1970-01-01
      • 2017-04-11
      • 2021-04-02
      • 1970-01-01
      • 2016-12-19
      • 2010-12-28
      • 2012-03-28
      相关资源
      最近更新 更多