有效的容器,可以保持订单,并且可以快速删除任何位置的元素

时间:2013-01-29 05:13:30

标签: c++ optimization containers

我正在寻找一个满足以下需求的C ++容器:

  • 我需要按索引删除元素。
  • 我删除一个元素后,我会在前面插入另一个元素(总是!!!!!)
  • 除此之外,尺寸没有变化。
  • 需要编入索引。
  • 存储在容器中的值是唯一的,作为索引。
  • 应将一个索引分配给一个值。除非我删除或添加一个值。然后应该调整索引。

对于另一组与该容器并行工作的数据,我需要一个具有这些功能的数据以及这些额外的数据:

  • 这个需要在两个方向上工作:由于它存储了一个唯一值,我需要能够非常快速地通过实际值(100%唯一)访问索引,因为这会发生很多次。

我无法承认,除了<<=

之外,任何值类型都支持==!=,...等运算符

如果有问题,请继续询问。 如果事情不清楚,我会进一步解释。

编辑:

自从我被要求以来,背后的实际问题就出现了:

我正在编写一个由模板容器类构成的库,它可以存储一定数量的对象。所有这些对象都是相同的类型。 (嗯,当然......)这些对象的另一个非常重要的属性必须是,它们可以通过唯一索引重新创建。这个指数也可以是任何东西。在这种情况下的一个示例是二维空间,您可以在其中创建平面上的对象,并且可以通过为对象类提供坐标(在本例中为单个对象)来重新创建所有属性。 现在当容器达到最大容量时,它会删除上次使用的对象。我的想法就在这里,你给容器提供了唯一的索引。如果仍然存储了所需的对象,则该函数返回对象上的指针并将其在内部容器内移动到前面。如果所需对象未存储在内部容器中,则将删除内部容器中的最后一个对象,并生成新对象并将其放在前面。

我需要这个,因为我有一个程序,可以轻松使用我的所有内存和更多。好吧,我每次都可以生成并销毁对象,但这似乎是对我的计算能力的浪费。所以我想出了这个只删除对象的容器,如果它已经使用了很长时间了。这在我的特殊情况下非常有用(巨大地图上的路径查找)

我希望有所帮助!

EDIT2:

确定。我会更清楚地说明这一点。

让我们从一个简单的数据缓存开始:

[0] d1  [1] d2  [2] d3  [3] d4

现在让我说我使用了d3。 现在结构应如下所示:

[0] d3  [1] d1  [2] d2  [3] d4

现在我向容器添加一个全新的元素(d5)。

[0] d5  [1] d3  [2] d1  [3] d2

这就是背后的想法。现在不是int - 值作为索引,我允许有一个自定义索引类,它能够恢复可能删除的每个单个对象(这不是问题。只是为了我的班级的要求工作)。

让我们从开头语句开始。对于第一种情况,这看起来像这样:

[0] i1  [1] i2  [2] i3  [3] i4
[i1] 0  [i2] 1  [i3] 2  [i4] 3

第二个例子如下:

[0] i3  [1] i1  [2] i2  [3] i4
[i1] 1  [i2] 2  [i3] 0  [i4] 3

最后状态看起来像这样:

[0] i5  [1] i3  [2] i1  [3] i2
[i1] 2  [i2] 3  [i3] 1  [i5] 0

我希望这更清楚。对于第二个,可能有多个容器。

5 个答案:

答案 0 :(得分:3)

抱歉 - 我发现你的问题有点模糊 - 但我会说明我认为你的要求必须是什么,然后再讨论数据结构的需求......

所以,我们已经像这样索引了数据 - 像这样(索引在括号中):

[0] a  [1] h  [2] b  [3] q

你的主要操作是删除/插入 - 说我们删除了元素2并插入值x,我们有:

[0] x  [1] a  [2] h  [3] q

所以,如果我们调用元素索引被删除 n ,给定我们已经有效完成的是沿着一个位置移动[0..n-1],然后用[...]覆盖[0]额外的价值。

讨论

如果使用矢量执行此操作,则移动操作可以任意大且相对较慢。但是如果你使用其他容器,例如关联容器(map,unordered map),你必须更新所有那些“移动”元素的键。其他常见的容器不提供O(log2N)或更好的索引,因此没有前途,所以让我们坚持使用vector并看看如何最小化疼痛。除了移动为O(N)之外,它涉及移动任意大的对象:在对象比指针大得多的情况下,你可以有一个指向对象的点数组,只需移动数组的指针而不移动对象:这可能会快得多(对于不喜欢被移动的对象也很有用,典型的原因是C ++ 03引入了移动运算符的C ++ 03复制速度慢。)

我想不出比这种矢量方法更好的东西。

回到你的问题的模糊性 - 如果你的“索引”令人困惑,只需要一个键控容器,但不需要对象来移动每个删除/插入操作的键,那么地图或无序地图是一个更好的选择。

答案 1 :(得分:2)

让我们来看看你的要求:

  • 按职位访问
  • 按元素访问
  • 在任意位置删除
  • 元素的单一性

如何?

  • 您需要使用随机访问来支持“按位置访问”,或者至少接近随机访问O(日志N)可能就足够了
  • 鉴于元素可能不支持排序,unicity将需要使用哈希集

我认为可以使用Boost.MultiIndex轻松实现。 examples部分已经提供了MRU缓存实现,并且您已足够接近。我建议合并:

这意味着:

typedef multi_index_container<
  T,
  indexed_by<
    random_access<>,
    hashed_unique</*...*/>
  > 
> cache_type;

注意:要使哈希索引生效,您需要同时支持==(或std::equal<T>的专业化) a哈希算子。如果类型尚未支持容器,则用户可以在声明容器时提供后者。

答案 2 :(得分:1)

如果您正在使用boost c ++库,请查看多索引容器。

http://www.boost.org/doc/libs/1_52_0/libs/multi_index/doc/index.html

答案 3 :(得分:0)

确定。谢谢大家,但我找到了一个非常有效的解决方案:

由于我的问题需要以下数据结构;

[0] d1  [1] d2  [2] d3  [3] d4
[0] i1  [1] i2  [2] i3  [3] i4
[i1] 0  [i2] 1  [i3] 2  [i4] 3

我决定使用这些容器:

  1. list
  2. list
  3. map
  4. 通过一些研究,我发现了std::advancestd::next,这使我能够有效地访问list中的某些元素。要更新地图,我将运行一个简单的for循环,这应该非常有效。

    如果您有更好的想法,请发布在这里!

    再次感谢你!

答案 4 :(得分:0)

只是想指出multi_index_container解决方案与使用向量进行随机访问支持具有相同的低效率:删除将具有数据结构大小的成本线性。在内部,random_access索引使用类似于指针向量的数据结构。

没有那么多的数据结构允许在优于线性的时间内随机访问和删除任何元素。 AFAIK std :: deque具有线性时间删除,除非该项接近序列的一端。

这给我们留下了平衡的二叉树(和变体,如跳过列表)。自平衡二叉树可以在固定开销下将子树的大小维持在任何节点下方,并且利用该“扩充”在O(log n)中实现随机访问。不幸的是,std :: map的默认实现通常没有此功能。因此,使用std :: map和std :: advance可以为您提供线性时间随机访问。您无法在std :: map“顶部”实现高效的随机访问,因为您的用户代码不知道std :: map在内部完成的树操作。

你可以自己动手(AVL树相对容易实现)。除非您愿意接受随机访问查找或删除+插入操作的不良性能,否则无法想到任何更简单的方法。不涉及树的“最不好”的解决方案可能是使用std :: deque

编辑:我提到的增强树数据结构称为http://en.wikipedia.org/wiki/Order_statistic_tree