【问题标题】:Hybrid vector/list container?混合向量/列表容器?
【发布时间】:2011-09-14 09:19:11
【问题描述】:

我需要一个同时具有向量和列表属性的容器。我需要快速随机访问容器中的元素,但我还需要能够在不移动其他元素的情况下移除容器中间的元素。我还需要能够遍历容器中的所有元素,并且一目了然(无需迭代)容器中有多少元素。

经过一番思考,我想出了如何创建这样一个容器,使用向量作为基本容器,并将实际存储的数据包装在一个结构中,该结构还包含记录元素是否有效的字段,并且指向向量中下一个/上一个有效元素的指针。结合一些重载等,听起来它应该相当透明并满足我的要求。

但在我真正开始创建另一个容器之前,我很好奇是否有人知道实现这个东西的现有库?我宁愿使用有效的东西,也不愿花时间调试自定义实现。我查看了 Boost 库(我已经在使用),但没有在其中找到它。

【问题讨论】:

  • 所以如果我插入100万个元素,然后我删除元素1-499999,然后我删除500001-999998,然后我删除元素500000......剩下的应该是collection[0]和collection [1]?还是应该是 collection[0] 和 collection[999999]?此外,当您遍历集合时,元素是否必须按照添加它们的顺序返回?
  • 它将是 collection[0] 和 collection[999999]。我使用元素的索引来引用该特定元素(因此需要随机访问),所以我不希望该索引改变我。而且我不在乎遍历列表的顺序是什么,只要我只需要触摸有效元素即可。
  • 你想要这个数据结构 for 做什么?

标签: c++ stl


【解决方案1】:

看起来您可能想要std::deque。删除元素不如std::list 有效,但因为双端队列通常是通过使用非连续内存“块”创建的,这些内存“块”通过容器内部的附加指针数组/向量进行管理(每个“块”都是N 个元素的数组),删除 deque 内的元素不会导致与向量相同的重新洗牌操作。

编辑: 不过,在查看了一些 cmets 之后,虽然我认为 std::deque 可以工作,但我认为 std::mapstd::unordered_map 实际上更适合您因为它允许您使用您想要的数组语法索引,但也可以快速删除元素。

【讨论】:

  • 不允许有效移除中间的元素。
  • std::deque 很好,但它不能很好地处理从容器中间删除元素。并且执行该删除将更改删除点之后的所有元素的索引。
  • 是的,指针的失效是不利的,但同样,您最终应该避免与从 std::vector 中间删除对象相关的复杂性。所以删除比向量快,但不如链表快。
  • 因此需要混合动力车。根据我的需要,我的列表中不断添加项目并从中删除旧项目。在这方面, std::list 将是完美的,除了我需要直接访问列表中的那些项目而无需先迭代并找到它。像向量一样,我需要存储一个项目,并立即知道它的索引位置(用作该项目的 ID)。
  • 那么std::mapstd::unordered_map 有什么问题?将映射的键设为 ID 值。然后,您应该通过 ID 值快速访问和相当有效的删除来充分利用这两个词。
【解决方案2】:

如果顺序无关紧要,我会使用哈希表将整数映射到指针。 std::tr1::unordered_map<int, T *>(或 std::unordered_map<int, unique_ptr<T>>,如果 C++0x 可以)。

哈希表的元素可以移动,这就是你需要使用指针的原因,但它会支持非常快速的插入/查找/删除。迭代也很快,但元素会以不确定的顺序出现。

或者,我认为您可以将自己的想法实现为std::vectorstd::list 的非常简单的组合。只需同时维护list<T> my_listvector<list<T>::iterator> my_vector。要添加一个对象,将它推到my_list 的后面,然后将它的迭代器推到my_vector 上。 (将迭代器设置为my_list.end() 并将其递减以获得最后一个元素的迭代器。)要查找,在向量中查找并取消对迭代器的引用。要删除,请从列表中删除(您可以通过迭代器执行此操作)并将向量中的位置设置为 my_list.end()

std::list 保证其中的元素在您删除时不会移动。

[更新]

我感到很有动力。首先通过一个实现:

#include <vector>
#include <list>

template <typename T>
class NairouList {
public:
  typedef std::list<T> list_t;
  typedef typename list_t::iterator iterator;
  typedef std::vector<iterator> vector_t;

  NairouList() : my_size(0)
  { }

  void push_back(const T &elt) {
      my_list.push_back(elt);
      iterator i = my_list.end();
      --i;
      my_vector.push_back(i);
      ++my_size;
  }

  T &operator[](typename vector_t::size_type n) {
      if (my_vector[n] == my_list.end())
          throw "Dave's not here, man";
      return *(my_vector[n]);
  }

  void remove(typename vector_t::size_type n) {
      my_list.erase(my_vector[n]);
      my_vector[n] = my_list.end();
      --my_size;
  }

  size_t size() const {
      return my_size;
  }

  iterator begin() {
      return my_list.begin();
  }

  iterator end() {
      return my_list.end();
  }

private:
  list_t my_list;
  vector_t my_vector;
  size_t my_size;
};

它缺少一些实现质量方面的内容...比如,您可能需要更多的错误检查(如果我删除相同的元素两次怎么办?),也许还有一些 const 版本的 operator[]begin()、@ 987654336@。但这是一个开始。

也就是说,对于“几千个”元素,地图可能至少也可以提供服务。一个好的经验法则是“在你的分析器告诉你之前永远不要优化任何东西”。

【讨论】:

  • 我曾经使用地图,但是当我访问容器中的元素时,我开始担心每次我想使用地图时都需要执行搜索以找到元素(尤其是当我在地图中有数千个元素时),而不是像向量一样直接访问元素。但是,我真的很喜欢你的第二个想法!我得再考虑一下。
  • unordered_map 通常比map 快。快得多。
  • 内存占用是速度的权衡。
  • @Nairou:数千个元素很少会对地图造成压力……即使平均有一百万个元素,它也需要将密钥与大约 20 个节点进行比较,然后才能找到匹配项。这可能非常快,特别是如果地图首先按intdouble 之类的简单类型进行排序,这种类型很少或永远不会相同(此后密钥可能包含其他区分字段)。即使是不共享长公共前缀的字符串也可以快速进行比较。尽管如此,map 查找比哈希映射可以访问更多的内存页面。在投入时间更改实施之前,您确实应该使用分析器。
  • @Nemo:啊对!所以实际上OP不想要随机访问,而是通过键快速查找......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-19
  • 2017-03-18
  • 1970-01-01
  • 1970-01-01
  • 2011-04-15
  • 1970-01-01
  • 2012-10-07
相关资源
最近更新 更多