在c ++中搜索合适的数据结构

时间:2012-07-03 21:32:30

标签: c++ data-structures

建议一个合适的数据结构(在C ++中),以便解决下面提到的目的:

  1. 将元素插入到结尾。
  2. 从末尾读取并删除元素。
  3. 从头开始阅读和删除元素。
  4. 查明是否存在特定元素。
  5. 现在我正在使用向量...但是查找某个特定元素是否存在时,向量中的时间复杂度很高,因为我的元素没有排序。

    是否有一些比矢量更好的数据结构来实现这一点..如果是......那么请给出一个例子。

6 个答案:

答案 0 :(得分:3)

一种可能性是使用std::setstd::unordered_set,它基本上是一个哈希表,并自己维护元素之间的顺序。这将为您提供O(log(n))或摊销的O(1)查找复杂性以及在开头/结尾的常量插入/删除。在Java中,这称为LinkedHashSet。不幸的是,STL并不提供开箱即用的这种数据结构,但它应该很容易在set / unordered_set或map / unordered_map之上实现。

这是一段代码,说明了这个想法:

template <typename T>
class linked_set {
 private:
  // Comparator of values with dereferencing.
  struct value_deref_less {
    bool operator()(const T *lhs, const T *rhs) const {
      return *lhs < *rhs;
    }
  };

  typedef std::set<const T*, value_deref_less> Set;
  Set set_;              // Used for quick lookup
  std::deque<T> store_;  // Used for ordered storage. deque is used instead of
                         // vector because the former doesn't invalidate
                         // pointers/iterators when elements are pushed.

 public:
  void push_back(const T& value) {
    store_.push_back(value);
    set_.insert(&store_.back());
    // TODO: handle the case of duplicate elements.
  }

  // TODO: better provide your own iterator.
  typedef typename Set::iterator iterator;

  iterator find(const T& value) { return set_.find(&value); }

  // ...
};

答案 1 :(得分:2)

您将无法在两侧进行快速插入和使用相同容器进行快速搜索,至少如果您将可能性限制为STL。更多异国情调的非标准容器可能有所帮助。

但我在这些情况下通常选择的方法是使用两个容器。对于存储元素,显而易见的选项是std :: deque。对于搜索,请创建一个std::map<K,V>,其中V是双端队列的迭代器。因为deques中的插入/删除不会使未涉及的迭代器无效,所以如果您始终记得同步地图和双端队列(即当您在双端队列上执行插入或删除时,也可以在地图上执行此操作),则应该没问题。 。 另一个更简单/更安全的选项,而不是使用迭代器 - 如果在地图中搜索后你只需要找到的元素(你不需要访问附近的元素等) - 就是在deque和map中都有智能指向实际对象的指针(更具体地说,shared_ptr)。同样,你必须小心保持同步;虽然如果它们失去同步不会是灾难性的,当然,你的程序的一致性可能会受到损害。

struct MyItem
{
    std::string name;
    int something;
    int another;
    MyItem(const std::string &name_, int something_, int another_)
        :name(name_), something(something_), another(another_) {}
};

class MyContainer
{
    public:
        typedef std::shared_ptr<MyItem> MyItemPtr;

        void push_front(MyItemPtr item)
        {
            deque.push_front(item);
            assert(map.find(item->name) == map.end());
            map[item->name] = item;
        }
        void push_back(MyItemPtr item)
        {
            deque.push_back(item);
            assert(map.find(item->name) == map.end());
            map[item->name] = item;
        }
        MyItemPtr pop_front()
        {
            item = deque.front();
            deque.pop_front();
            map.erase(item->name);
            return item;
        }
        MyItemPtr pop_back()
        {
            item = deque.back();
            deque.pop_back();
            map.erase(item->name);
            return item;
        }
        MyItemPtr find(const std::string &name)
        {
            std::map<std::string, MyItemPtr>::iterator iter = map.find(name);
            if (iter == map.end())
                return MyItemPtr();
            else
                return iter->second;
        }

    private:
        std::deque<MyItemPtr> deque;
        std::map<std::string, MyItemPtr> map;
};

使用它:

MyContainer container;

MyContainer::MyItemPtr a(new MyItem("blah", 1, 2));
container.push_back(a);
MyContainer::MyItemPtr b(new MyItem("foo", 5, 6));
container.push_front(b);

MyContainer::MyItemPtr f = container.find("blah");
if (f)
    cout << f->name << ", " << f->something << ", " << f->another;

答案 2 :(得分:1)

您应该从std::map开始,看看对数复杂度是否合适。

B +树会有点复杂,需要您自己的实施或研究才能找到开源的实施方案。但如果std::map仍然证明不合适,那么根据您引用的要求和痛点(搜索),这是一个合理的选择。

例如,您可以将元素的值映射到std::list中的迭代器。所有操作都是O(lg n)std::map

答案 3 :(得分:1)

您可以保留vector,但也可以使用std::set进行快速查询。

该集合不足以从开头/结尾删除元素,因为您实际上并不知道哪个是您插入的第一个/最后一个元素。您可以保留对这些元素的引用,但是为了支持删除,您将需要下一个等等,这会降级为使用一个容器。

答案 4 :(得分:0)

使用std::deque。这是一个双端队列,它也可以用作标准接口的容器,例如std::stack

它通常使用准链接列表实现,并且在边缘插入和删除时分摊O(1)时间复杂度。

答案 5 :(得分:0)

如果有很多插入/删除链接列表会更合适 请注意链接列表(单个或双重)将产生相当大的开销(通常是指针的大小,但实现方式各不相同)。

标准模板库为您提供std :: list。