【问题标题】:Looking for special C++ data structure寻找特殊的 C++ 数据结构
【发布时间】:2011-10-24 01:06:21
【问题描述】:

我正在寻找满足以下条件的数据结构(或数据结构的组合)的 C++ 实现:

  • 访问项目的方式与在 std::vector 中的方式相同
  • 提供随机访问迭代器(连同迭代器比较)
  • 平均项目 访问(:lookup) 时间最多为 O(log(n)) 复杂度
  • 项目的迭代顺序与添加到容器中的顺序相同
  • 给定一个迭代器,我可以找出容器中指向的项目的序数位置,最坏的情况是O(log(n)) 复杂度
  • 在最坏的O(log(n))复杂度在特定位置提供项目插入和删除
  • 移除/插入项目不会使之前获得的迭代器失效

提前感谢您的任何建议

达利博尔

(编辑)答案:

我选择的答案描述了满足所有这些要求的数据结构。然而,正如 Maxim Yegorushkin 所建议的, boost::multi_index 提供的功能与上述功能非常接近。

(编辑)某些要求未正确指定。根据更正(:original)

进行修改

(编辑)我找到了接受的答案中描述的数据结构的实现。到目前为止,它按预期工作。它叫counter tree

(编辑)考虑使用 sp2danny 建议的 AVL-Array

【问题讨论】:

  • 您不能拥有多行 cmets。最好编辑问题。
  • 也许是一个 boost.multi_index 和一个底层向量加上一个用于快速查找的集合类型索引...
  • 在标准库中没有这样的容器可以满足您的所有要求。另外,即使理论上我也不相信存在这样的容器。您应该优先考虑您的要求,以便我们可以想到最合适的东西
  • 您的要求列表排除了使用任何已知的 single 数据结构。你会遇到所有这些的唯一方法是使用组合多个数据结构的东西(例如Kerrek提到的boost::multi_index
  • 如果有这样的容器,那将是标准库中唯一的一个。

标签: c++ data-structures iterator complexity-theory


【解决方案1】:

请参阅此处:STL Containers(向下滚动页面以查看有关算法复杂性的信息),我认为 std::deque 符合您的要求。

【讨论】:

  • deque 如何实现按值快速查找?此外,容器修改确实使迭代器无效(但不是对元素的引用)。
  • deque 是最坏的情况 O(N) 查找和删除,IIRC。
  • 我不认为deque 满足插入/删除约束(前端和末尾的插入/删除除外)。请参阅 n3290.pdf C++0x 初步标准中的 23.3.3.4。
  • 对于deque的插入和删除可能没有O(log(n))的时间复杂度
  • @Aditya:正确。插入和/或删除应该是O(1) 从任一端或O(N) 其他任何地方。
【解决方案2】:

根据您的要求 boost::multi_index 使用两个索引就可以了。

第一个索引是ordered index。它允许 O(log(n)) 插入/查找/删除。第二个索引是random access index。它允许随机访问,并且元素按插入顺序存储。对于这两个索引,当其他元素被删除时,迭代器不会失效。从一个迭代器转换到另一个迭代器是 O(1) 操作。

【讨论】:

  • 我猜你必须小心跟踪你拥有的迭代器。此外,是否按O(N)O(N*log(N)) 的顺序遍历所有项目? IE。是迭代器的operator++O(1)还是O(log(N))
  • 有序索引的文档没有说明operator++ 的复杂性。但是,它实现为红黑树,所以迭代器的operator++的复杂度与std::setstd::map的复杂度相同。即 O(1)。
  • 迭代所有项目的复杂性并不重要。我想我在提供的规范中不清楚。我只需要按插入的顺序访问这些项目。我没有别的东西可以订购它们(那将是有用的),所以我认为这不符合 boost::multi_index 的有序索引。随机访问索引似乎几乎可以解决问题,除了在序列末尾以外的任何位置插入/删除元素具有 O(log(n)) 复杂度
  • @Dalibor:看来你对boost::multi_index 不熟悉。我建议您阅读Tutorial,然后重新阅读我的答案。
【解决方案3】:

让我们来看看这些......

  • 平均项目查找时间最糟糕的是 O(log(n)) 复杂度
  • 移除/插入项目不会使之前获得的迭代器失效
  • 提供最坏 O(log(n)) 复杂度的项目插入和删除

这几乎是在尖叫“树”。

  • 提供随机访问迭代器(连同迭代器比较)
  • 给定一个迭代器,我可以找出容器中指向的项目的序数位置,最坏的情况是 O(log(n)) 复杂度
  • 项目的迭代顺序与添加到容器中的顺序相同

我假设您提供的随机访问迭代器的索引是按插入顺序排列的,因此[0] 将是容器中最旧的元素,[1] 将是下一个最旧的元素,依此类推。意味着,在删除时,为了使迭代器有效,迭代器内部不能存储索引,因为它可能会在没有通知的情况下更改。因此,仅使用 map 并以插入顺序为键是行不通的。

鉴于此,除了通常的成员之外,树的每个节点都需要跟踪每个子树中有多少元素。这将允许使用O(log(N)) 时间进行随机访问。我不知道有一套现成的代码,但子类化 std::rb_treestd::rb_node 将是我的出发点。

【讨论】:

  • 这正是我的想法,但我不知道任何现有的实现。
  • 这就是为什么我说你可能不得不从标准库红黑树代码开始自己推出。
  • 从未听说标准库有rb_treerb_node
  • rb_tree 和 rb_node 没有记录,因为它们是提供的 STL 容器的实现细节(内部使用)。
【解决方案4】:

AVL-Array 应该符合要求。

【讨论】:

  • 不知道为什么这被否决了。根据文档,引用的数据结构满足要求。我唯一缺少的是能够告诉给定迭代器的对象在数组中的序数位置。我可能忽略了文档中的某些内容?
  • 源中缺少迭代器的序号位置。我自己也需要它,并将其添加为单线。
【解决方案5】:

这是我的符合要求的“lv”容器,O(log n) 插入/删除/访问时间。 https://github.com/xhawk18/lv

容器是只有头文件的 C++ 库, 并且具有与其他 C++ 容器相同的迭代器和函数,例如列表和向量。

“lv”容器是基于rb-tree的,它的每个节点都有一个关于子树中节点数量的大小值。通过检查树的左/右孩子的大小,我们可以快速随机访问节点。

【讨论】:

  • 谢谢。它与问题编辑中提到的 counter_tree 和 AVL-array 相比如何?
  • 嗨,Dalibor,它是一种计数器树的实现,以rb-tree为基础数据结构。
猜你喜欢
  • 1970-01-01
  • 2011-01-13
  • 2012-07-06
  • 2013-02-20
  • 1970-01-01
  • 2011-11-23
  • 1970-01-01
  • 2014-09-27
  • 1970-01-01
相关资源
最近更新 更多