【问题标题】:How to make STL's priority_queue fixed-size如何使 STL 优先级队列固定大小
【发布时间】:2012-07-20 09:29:50
【问题描述】:

我正在创建一个简单的游戏,我使用std::priority_queue小队 发出命令(每个小队都有一个priority_queue<command>)。

机器人每 20 秒分析一次情况并向priority_queue 发送命令。

如何使priority_queue 固定大小,例如将大小设置为10?期望的效果是,当达到最大值时,如果我向队列中添加 2 个新命令,则会自动删除 2 个优先级最低的现有命令。

【问题讨论】:

  • 这是一个新的。幸运的是,我从来没有这样做过。我怀疑@DeadMG 有唯一明显的解决方案 - 您将不得不编写一些代码来执行此操作,锁定队列并对其进行迭代:(
  • Here 是访问priority_queue 的底层容器的肮脏黑客(示例使用stack,但原理相同)。不过可能有更好的解决方案。
  • priority_queue 在底层容器中实现二进制排序堆。它不是为迭代而设计的——而是让顶部元素最大,并让pushpop 在日志时间内执行。

标签: c++ stl std


【解决方案1】:

将它包装在另一个将为您执行此操作的类中。该标准本身不提供此类功能。

【讨论】:

  • 正确的解决方法见下面于志昌的评论
【解决方案2】:

这是偷偷摸摸的,但你应该能够覆盖std::priority_queue 的功能来做你需要的事情。这似乎在我所做的一些测试中有效:

template<typename T>
class fixed_priority_queue : public std::priority_queue<T> 
{
  public:
    fixed_priority_queue(unsigned int size) : fixed_size(size) {}
    void push(const T& x) 
    { 
      // If we've reached capacity, find the FIRST smallest object and replace
      // it if 'x' is larger
      if(this->size() == fixed_size)
      {
        // 'c' is the container used by priority_queue and is a protected member.
        auto beg = c.begin(); auto end = c.end();
        auto min = std::min_element(beg, end);
        if(x > *min)
        {
            *min = x;
            // Re-make the heap, since we may have just invalidated it.
            std::make_heap(beg, end);
        }
      }
      // Otherwise just push the new item.
      else          
      {
        priority_queue::push(x);
      }
    }
  private:
    fixed_priority_queue() {} // Construct with size only.
    const unsigned int fixed_size;
    // Prevent heap allocation
    void * operator new   (size_t);
    void * operator new[] (size_t);
    void   operator delete   (void *);
    void   operator delete[] (void*);
};

这里发生了什么?

  • 扩展std::priority_queue
  • 覆盖priority_queue::push() 方法,用新项目交换最低项目
  • 默认构造函数是私有的,没有大小就不能构造
  • 限制堆上的分配,因为 STL 容器没有虚拟析构函数。

使用方法:

const unsigned int LIMIT = 20;
fixed_priority_queue<int> fooQueue(LIMIT);

// Testing.
for(int i=0; i<40; i++)
  fooQueue.push(rand());
for(int i=0; i<LIMIT; i++)
{
  printf("%i\n", fooQueue.top());
  fooQueue.pop();
}

这里有什么不好?

  • 您不能安全地在堆上创建这些队列,因此大队列可能是不可能的。 20 左右,正如你提到的,无论如何在堆栈上应该没问题(取决于对象)。我可能会避免大排长队,因为...
  • 我不确定这里的性能。 priority_queue 在底层容器上调用make_heap(默认为std::vector)。我不确定它通常多久调用一次,但如果队列已满,我们会经常调用它。我想它也可以在priority_queue::push() 内调用?
  • 可能一堆,所以我欢迎读者的所有建设性反馈和编辑:)

希望这是有用的,如果不是至少有趣的话。

【讨论】:

  • 我认为你应该在这里使用私有继承。
  • 你知道你的fixed_priority_queue不是std::priority_queue吗?因此,请使用私有继承或组合。
【解决方案3】:

Aryabhatta's answer of another question 适用于这个问题。

您使用最大堆。

假设你有一个 N 元素堆(实现为一个数组),它 包含迄今为止看到的 N 个最小的元素。

当一个元素进入时,您检查最大(O(1) 时间),并且 如果大于则拒绝。

前面几个cmets提到的迭代是不必要的。

【讨论】:

  • 这应该是向上的方式,这是标准方式,可能也是最有效的方式。
【解决方案4】:

一个想法是创建一个最小优先级队列并使用 size() 方法仅将优先级队列填充到所需的级别。像这样的:

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

struct Compare {
    // priority queue is max heap by default, use compare
    // to make it minheap
    bool operator() (const int& a, const int& b) {
        return a>b;
    }
};

typedef priority_queue<int, vector<int>, Compare> pqSize;

void priorityQueueFixedSize(pqSize& pq, int size, vector<int>& vec) {
    for (int i=0;i<vec.size();i++) {
        if (pq.size() < size) {
            pq.push(vec[i]);
        } else {
            if (vec[i] > pq.top()) {
                pq.pop();
                pq.push(vec[i]);
            }
        }
    }
}

void printPQ(pqSize& pq) {
    while (!pq.empty()) {
        cout << pq.top() << " ";
        pq.pop();
    }
    cout << endl;
}

int main() {
    vector<int> vec(20,0);
    for (int i=0;i<vec.size();i++) {
        vec[i] = i;
    }
    pqSize pq;
    priorityQueueFixedSize(pq,10, vec);
    printPQ(pq);
}

这样,优先级队列中只会保留最多 10 个元素。

【讨论】:

    【解决方案5】:

    标准提供了一组原始堆操作:

    make_heap
    sort_heap
    push_heap
    pop_heap
    

    我们可以将它包装在一个简单的类中,该类以Command[10]std::array&lt;Command, 10&gt; 作为其数据成员,或者以较低级别的方式简单地使用上面的四个函数。

    我个人建议不要将它包装在一个函数中,因为您可能希望采用 ECS 模式并在您的命令上应用更多可能的操作,而不仅仅是作为优先级队列。正如我们所知,如果您使用优先队列,则可能会出现不必要的复制或移动。

    【讨论】:

      【解决方案6】:

      有点棘手的解决方案是不使用 std::priotity_queue,而是使用大小为 N+1 的 std::array,其中只有前 N 个元素是真正的队列。而不是替换具有最低优先级的元素,而是用新命令覆盖queue.back()。比pop_queue(q.begin(), q.end()) 将新命令添加到队列中。

      这是我的代码,有效:

      int main() {
          std::array<int, 10> queue;
          queue.fill(500);
       
          for(int i = 0; i < 1000; i++)
          {
              queue.back() = rand() % 1000;
              pop_heap(queue.begin(), queue.end(), std::greater<int>());
              //print_queue(queue);
              assert(is_heap(queue.begin(), queue.end()-1, std::greater<int>()));
          }
      }
      

      另一件事是,您的规范是否是您真正想要的。不要忘记将年龄添加到优先级中。如果你不这样做,那么在一百万次迭代之后,你将拥有 10 个元素,具有最高优先级。何时将它们添加到队列中无关紧要。

      【讨论】:

        猜你喜欢
        • 2016-04-13
        • 1970-01-01
        • 1970-01-01
        • 2013-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多