【问题标题】:Queue with unique entries in c++在 C++ 中具有唯一条目的队列
【发布时间】:2012-07-27 20:31:33
【问题描述】:

我需要在 C 或 C++ 中实现一个包含唯一条目(无重复项)的队列。我正在考虑维护队列中已有元素的引用,但这似乎效率很低。

请告诉我您解决此问题的建议。

【问题讨论】:

  • 你尝试了什么?你会保持对已经成员的引用吗?为什么效率低?
  • 如果您确实需要队列的 FIFO 行为以及唯一性,也许 Boost multi_index 容器是一个合适的选择。
  • 队列维护项目的顺序是排序的还是未排序的?如果要对队列中的项目进行排序,那么维护任何类型的辅助引用并不是绝对必要的。

标签: c++ c data-structures


【解决方案1】:

如何使用辅助数据结构来跟踪唯一性:

std::queue<Foo> q;
std::set<std::reference_wrapper<Foo>> s;

// to add:

void add(Foo const & x)
{
    if (s.find(x) == s.end())
    {
        q.push_back(x);
        s.insert(std::ref(q.back()));  // or "s.emplace(q.back());"
    }
}

或者,或者,颠倒队列和集合的角色:

std::set<Foo> s;
std::queue<std::reference_wrapper<Foo>> q;

void add(Foo const & x)
{
    auto p = s.insert(x);       // std::pair<std::set<Foo>::iterator, bool>
    if (s.second)
    {
        q.push_back(std::ref(*s.first));  // or "q.emplace_back(*s.first);"
    }
}

【讨论】:

  • 为什么不用自定义容器进行适配?
  • 您没有询问 OP 是否必须对队列进行排序。如果队列未排序,则索引是无用的,因为在添加之前必须扫描队列以找到项目的插入位置。
  • @aps2012:排序队列的意义何在?队列在其定义上就是一个 FIFO 容器。
  • 你错了。队列不是严格的 FIFO。任何类型的Priority Queue(每个项目都附加了优先级的队列)都会被排序,因为分配给每个项目的优先级会强制执行顺序。在您否决其他人的答案之前,请检查事实
  • @aps2012 如果他需要,我假设 OP 会要求 priority_queue
【解决方案2】:

排队:

  • 使用 std::set 维护您的独特元素集
  • 将您能够添加到 std::set 的任何元素添加到 std::queue

出队:

  • 从 std::queue 和 std::set 中移除元素

【讨论】:

    【解决方案3】:

    std::queue 是一个容器适配器,它使用了相对较少的底层Container 成员。您可以轻松实现包含以下两者的自定义容器:reference_wrapper&lt;T&gt;unordered_mapdeque&lt;T&gt;。它至少需要成员frontpush_back。当你的容器的push_back 被调用时检查hash_map 并相应地拒绝(可能抛出)。举个完整的例子:

    #include <iostream>
    #include <set>
    #include <deque>
    #include <queue>
    #include <unordered_set>
    #include <functional>
    
    namespace std {
    
    // partial specialization for reference_wrapper
    // is this really necessary?
    template<typename T>
    class hash<std::reference_wrapper<T>> {
    public:
      std::size_t operator()(std::reference_wrapper<T> x) const 
      { return std::hash<T>()(x.get()); }
    };
    
    }
    
    template <typename T>
    class my_container {
      // important: this really needs to be a deque and only front
      // insertion/deletion is allowed to not get dangling references
      typedef std::deque<T> storage;
      typedef std::reference_wrapper<const T> c_ref_w;
      typedef std::reference_wrapper<T> ref_w;
    public:  
      typedef typename storage::value_type value_type;
      typedef typename storage::reference reference; 
      typedef typename storage::const_reference const_reference; 
      typedef typename storage::size_type size_type;
    
      // no move semantics
      void push_back(const T& t) {
        auto it = lookup_.find(std::cref(t));
        if(it != end(lookup_)) {
          // is already inserted report error
          return;
        }
        store_.push_back(t);
        // this is important to not have dangling references
        lookup_.insert(store_.back());
      }
    
      // trivial functions
    
      bool empty() const { return store_.empty(); }
      const T& front() const { return store_.front(); }
      T& front() { return store_.front(); }
    
      void pop_front() { lookup_.erase(store_.front()); store_.pop_front();  }
    private:
      // look-up mechanism
      std::unordered_set<c_ref_w> lookup_;
      // underlying storage
      storage store_;
    };
    
    int main()
    {
      // reference wrapper for int ends up being silly 
      // but good for larger objects
      std::queue<int, my_container<int>> q;
      q.push(2);
      q.push(3);
      q.push(2);
      q.push(4);
      while(!q.empty()) {
        std::cout << q.front() << std::endl;
        q.pop();
      }
    
      return 0;
    }
    

    编辑:你会想要使my_container 成为一个合适的容器模型(也许还有分配器),但这是另一个完整的问题。感谢 Christian Rau 指出错误。

    【讨论】:

    • 不是也需要pop_front(或者更确切地说是pushpop)吗?
    • @KerrekSB 啊,是的。刚刚从我的脑海中输入了那个列表。 empty 和一堆 typedef。它仍然是相当少的,并产生了一个更清洁的解决方案 IMO。
    • @pmr +1 非常好的解决方案。但我认为它还需要backsizeemplace_back(也许还需要swap,但这可以通过默认swap 和默认生成的移动构造函数/赋值(如果有)来有效处理)。我认为您的意思是std::hash&lt;T&gt; 而不是std::hash&lt;int&gt;
    • 你为什么不把它从lookup_弹出到pop_front
    • @ChristianRau 你有一个错误。
    【解决方案4】:

    您的问题中没有提到一个非常重要的点,那就是您的项目队列是排序还是具有某种排序(称为Priority queue),或 unsorted(称为普通 FIFO)。您选择的解决方案将仅取决于此问题的答案。

    1. 如果您的队列未排序,那么在队列之外维护一个额外的数据结构会更有效。使用以某种方式排序的第二个结构来维护队列的内容将允许您检查队列中是否已经存在项目,或者扫描队列本身的速度不会快得多。添加到未排序队列的末尾需要恒定的时间,并且可以非常有效地完成。

    2. 如果您的队列必须排序,那么将项目放入队列需要您知道项目在队列中的位置,这需要队列无论如何都要被扫描。一旦你知道了一个项目的位置,你就知道这个项目是否是重复的,因为如果它是重复的,那么一个项目将已经存在于队列中的那个位置。在这种情况下,所有工作都可以在队列本身上以最佳方式执行,无需维护任何辅助数据结构。

    数据结构的选择取决于您。但是,对于 (1) 二级数据结构不应是任何类型的列表或数组,否则扫描二级索引将不会比扫描原始队列本身更有效。

    【讨论】:

      猜你喜欢
      • 2020-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-09
      相关资源
      最近更新 更多