有效的容器,可保持秩序并快速从任何位置删除元素
Posted
技术标签:
【中文标题】有效的容器,可保持秩序并快速从任何位置删除元素【英文标题】:Effective container that keeps order and is fast at deleting elements from any position 【发布时间】:2013-01-29 05:13:29 【问题描述】:我正在寻找满足我以下需求的 C++ 容器:
我需要按索引删除元素。 删除一个元素后,我会在前面插入另一个元素(总是!!!!!) 除此之外,大小没有变化。 它需要被索引。 存储在容器中的值是唯一的,作为索引。 应将一个索引分配给一个值。除非我删除或添加一个值。然后应该调整索引。对于另一组与该容器并行工作的数据,我需要一个具有这些功能和这些附加功能的数据:
这个需要在两个方向上工作:由于它存储一个唯一值,我需要能够非常快速地通过实际值(100% 唯一)访问索引,因为这会发生很多次。除了==
和!=
之外,我不能保证任何值类型都支持<
、<=
、...等运算符
如果有问题,请继续提问。 如果有不清楚的地方,我会进一步解释。
编辑:
既然我被要求了,这背后的实际问题就来了:
我正在编写一个由模板容器类组成的库,它能够存储一定数量的对象。所有这些对象都是同一类型。 (嗯,当然......)这些对象的另一个非常重要的属性必须是,它们可以通过唯一索引重新创建。这个索引也可以是任何东西。在这种情况下,一个示例是二维空间,您可以在其中创建平面上的对象,并且可以通过为对象类提供坐标来重新创建所有属性(在这种情况下作为单个对象)。 现在,当容器达到最大容量时,它会删除最后使用的对象。我的想法是在这里,你给容器唯一的索引。如果仍存储所需的对象,则该函数返回对象上的指针并将其在内部容器内移动到前面。如果需要的对象没有存储在内部容器中,则内部容器中的最后一个对象将被删除并生成新的对象并放在最前面。
我需要这个,因为我有一个程序可以轻松使用我所有的 RAM 甚至更多。好吧,我每次都可以生成和销毁对象,但这对我来说似乎是在浪费计算能力。所以我想出了这个只删除对象的容器,如果它没有被使用很长时间的话。这在我的特殊情况下非常有用(HUGE 地图上的路径查找)
希望对你有帮助!
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
-values 作为索引,我允许有一个自定义索引类,它能够恢复每个可能被删除的对象(这不是问题。只是我的类工作的要求)。
让我们从开头的语句开始。第一种情况是这样的:
[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
我希望这样可以更清楚。对于第二个,可能有多个容器。
【问题讨论】:
您是否分析过类似std::deque
的内容,看看它是否足够快满足您的需求?
我已经阅读了有关这些属性的信息,但这似乎不是一个好的选择。像所有其他流行的容器一样。我什至不介意使用 boost 中的容器。
更像你需要 std::map
devmentor.org/references/stl/stl.php std::list 看起来像你要找的东西
@billz:我认为通过索引,OP 意味着 O(1) 通过表示列表中序数位置的数字键进行访问。
【参考方案1】:
抱歉 - 我觉得你的问题有点含糊 - 但我会说明我认为你的要求必须是什么,然后讨论数据结构的需求......
所以,我们有类似这样的索引数据(索引在括号中):
[0] a [1] h [2] b [3] q
您的主要操作是删除/插入 - 假设我们确实删除了元素 2 并插入值 x,我们会有:
[0] x [1] a [2] h [3] q
所以,如果我们调用被删除的元素索引 n,假设我们实际上所做的是将 [0..n-1] 沿一个位置移动,然后用额外的价值。
讨论
如果您使用向量执行此操作,则移动操作可以任意大且相对较慢。但是,如果您使用其他一些容器,例如关联容器(地图、无序地图),则无论如何您都必须更新所有这些“移动”元素的键。其他常见的容器不提供 O(log2N) 或更好的索引,所以没有希望,所以让我们坚持使用向量,看看如何最小化痛苦。除了移动是 O(N) 之外,它还涉及移动任意大的对象:在对象比指针大得多的情况下,您可以拥有一个指向对象的数组,并且只需移动数组的指针而不移动对象:这可能会快得多(它对于不喜欢被移动的对象也很有用,典型的原因是 C++11 引入了移动运算符的 C++03 复制速度慢)。
我想不出比这种矢量方法更好的方法了。
回到您的问题的模糊性 - 如果您将“索引”与“键”混淆并且只需要一个键控容器但不需要对象在每次删除/插入操作时移动其键,那么映射或无序映射是一个更好的选择。
【讨论】:
这似乎是一个不错的方法。将元素添加到后面而不是前面是否有意义?向量类不会自己自动调整一切吗? 在后面添加元素并不一定更好......主要结果是如果你碰巧通常在索引后面找到下一个元素,那么对象就会更少 -移动参与,但我认为没有理由假设会发生这种情况。只是要清楚一点-将元素插入到您要删除的元素碰巧所在的位置会更快-然后不需要复制/移动其他元素。续 ...向量类不会自动移动元素:向量表示连续存储的索引区域...计算机内存系统通常不知道特定内存是否为数组/ vector 正在使用中,程序本身必须移动内容以重新打包数组以使其保持连续,以便可以使用 baseAddress + sizeof(Element) * index 方法对其进行快速索引。 是的。我估计。这就是为什么我选择list
和std::advance
的原因。不过真的谢谢你!!
可能对你有用,但是使用std::advance
从第一个元素一直导航到任意索引 n 与直接索引相比会非常慢:可能足够了完全阻止您的程序运行良好。【参考方案2】:
让我们看看您的要求:
按位置访问 按元素访问 在任意位置删除 元素的单一性怎么做?
您需要一些带有随机访问的东西来支持“按位置访问”,或者至少接近随机访问 O(log N) 可能就足够了 鉴于元素可能不支持排序,唯一性需要使用哈希集我认为这可以使用 Boost.MultiIndex 轻松实现。 examples 部分已经提供了 MRU 缓存实现,您已经足够接近了。我建议结合:
Random Access Index:按位置产生访问和删除 带有Hashed Index:产生按元素访问和唯一性检查这意味着类似:
typedef multi_index_container<
T,
indexed_by<
random_access<>,
hashed_unique</*...*/>
>
> cache_type;
注意:要使 散列索引 工作,您需要同时支持 ==
(或 std::equal<T>
的特化)和散列函子。如果类型不支持后者,则用户可以在声明容器时提供后者。
【讨论】:
感谢您的回答。但我对此有几个问题:它快速/有效吗?如果没有,这不是一个选择! “标准”索引会自动适应吗?意思是,如果我删除一个对象,索引孔会被关闭吗?像 1 2 3 变成 1 2 吗? @YannickSchinko:1/ 您自己提出的解决方案可能会更快,但这是否足以满足您的需求完全是另一回事。最值得注意的是:它比大多数“多容器”解决方案更节省空间 2/ 是的,所有索引都会自动保持同步;这实际上是该解决方案的一大卖点:没有对象是“部分”存在的。【参考方案3】:如果您使用的是 boost c++ 库,请查看多索引容器。
http://www.boost.org/doc/libs/1_52_0/libs/multi_index/doc/index.html
【讨论】:
无论如何我都在考虑使用 boost。但我不知道,有这样的东西存在!谢谢!【参考方案4】:好的。谢谢大家,但我找到了一个非常有效的解决方案:
由于我的问题需要以下数据结构;
[0] d1 [1] d2 [2] d3 [3] d4
[0] i1 [1] i2 [2] i3 [3] i4
[i1] 0 [i2] 1 [i3] 2 [i4] 3
我决定使用这些容器:
list
list
map
通过一些研究,我发现了std::advance
和std::next
,这使我能够有效地访问list
中的某些元素。为了更新地图,我将只运行一个简单的 for 循环,它应该会非常有效。
如果您有更好的想法,请在此处发布!
再次感谢您!
【讨论】:
list
可能是您可以使用的最糟糕的容器:不适合缓存、体积庞大等...请注意,std::advance
对于列表而言是 O(N)(即,如果您要求前进 18 次,将前进 18 次)。对象的 vector
或 unique_ptr
可能会更有效。
由于我删除的比搜索的多,我真的认为列表应该更快,因为删除和添加只是 O(1)。
这取决于您拥有的物品数量,以及移动/复制它们的成本。请注意,Big O 复杂性仅对 huge 卷有意义,对于较小的卷(~100/~1000 项),常数因素更重要。【参考方案5】:
只是想指出 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。
【讨论】:
以上是关于有效的容器,可保持秩序并快速从任何位置删除元素的主要内容,如果未能解决你的问题,请参考以下文章