20

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

请让我知道您解决此问题的建议。

4

4 回答 4

11

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

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);"
    }
}
于 2012-07-30T07:29:08.933 回答
9

排队:

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

出队:

  • 从 std::queue 和 std::set 中移除元素
于 2012-07-30T07:28:28.013 回答
7

std::queue是一个容器适配器,使用的底层成员相对较少Container。您可以轻松实现包含以下内容的自定义容器: an unordered_mapofreference_wrapper<T>和 a deque<T>。它至少需要成员frontpush_back. 检查内部hash_map何时push_back调用容器并相应地拒绝(可能抛出)。举个完整的例子:

#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 指出错误。

于 2012-07-30T07:39:17.857 回答
3

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

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

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

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

于 2012-07-30T07:48:59.753 回答