如何告诉std :: priority_queue刷新它的排序?

时间:2011-04-27 20:31:27

标签: c++ stl priority-queue

我有一个指向struct city的指针优先级队列。我修改优先级队列之外的这些指针指向的对象,并希望告诉优先级队列根据新值“重新排序”自己。

我该怎么办?

示例:

#include <iostream>
#include <queue>

using namespace std;

struct city {
    int data;
    city *previous;
};

struct Compare {
    bool operator() ( city *lhs, city *rhs )
    {
        return ( ( lhs -> data ) >= ( rhs -> data ) );
    }
};

typedef priority_queue< city *, vector< city * >, Compare > pqueue;

int main()
{
    pqueue cities;

    city *city1 = new city;
    city1 -> data = 5;
    city1 -> previous = NULL;
    cities.push( city1 );

    city *city2 = new city;
    city2 -> data = 3;
    city2 -> previous = NULL;
    cities.push( city2 );

    city1 -> data = 2;
    // Now how do I tell my priority_queue to reorder itself so that city1 is at the top now?

    cout << ( cities.top() -> data ) << "\n";
    // 3 is printed :(

    return 0;
}

5 个答案:

答案 0 :(得分:8)

基于http://www.sgi.com/tech/stl/priority_queue.html,看起来没有办法做到这一点,没有清空和重新插入。

如果你愿意离开priority_queue(但仍然想要一个堆),那么你可以使用一个向量,以及make_heappush_heappop_heap。请参阅priority_queue页面中的“备注”部分。

答案 1 :(得分:8)

这有点骇人听闻,但没有任何违法行为,它完成了工作。

std::make_heap(const_cast<city**>(&cities.top()),
               const_cast<city**>(&cities.top()) + cities.size(),
               Compare());

<强>更新

如果出现以下情况,请不要使用此黑客:

  • 基础容器不是vector
  • Compare仿函数的行为会导致外部副本的订购方式与Compare中存储的priority_queue副本不同。
  • 您不完全了解这些警告的含义。

您始终可以编写自己的容器适配器来包装堆算法。 priority_queue只不过是make/push/pop_heap的简单包装。

答案 2 :(得分:7)

如果您需要保留有序集合,可以考虑以下解决方案:使用std::set并更新值删除项目,更新其值并将其放回集合中。这将为您提供更新一个项目的O(log n)复杂度,这是在通常的堆结构中最好的(假设您可以通过筛选筛选程序访问其内部组件)。 std::set的唯一缺点是初始化具有n个项目的集合的时间。 (O(n log n)代替O(n))。

答案 3 :(得分:3)

这是一个老问题,但当我想自己做这个时,我对任何答案都不满意。没有任何黑客攻击。 std::priority_queue包含合法和惯用的所有机制。

std::priority_queue有两个非常有用的数据成员c(基础容器)和comp(比较谓词)。

同样有用的是,标准要求Container模板类型必须是SequenceContainer的模型,其迭代器是RandomAccessIterator的模型。

这很有用,因为Iter std::make_heap参数类型具有相同的RandomAccessIterator模型要求。

这是一种冗长的说法,std::priority_queue是堆的包装,因此std::make_heap(std::begin(c), std::end(c), comp)必须是有效的表达式。

“坏”新闻是ccomp受到保护。这实际上是个好消息有两个原因:

  1. 您无法意外破坏堆。

  2. 如果您从std::priority_queue派生,则可以有意修改堆。

  3. 所以诀窍是从std::priority_queue派生你的优先级队列,在一个成员函数中,以任何你喜欢的方式改变内部堆c,然后调用std::make_heap(std::begin(c), std::end(c), comp);将它转回一个有效的堆。

      

    从STL容器继承是不是一般的坏主意

    嗯,是的,但是......

    对于年轻人和/或不谨慎的人来说,这可能是一个坏主意有两个原因。缺乏多态析构函数和切片风险。

    1. 多态析构函数
    2. 通过指向其基类的指针,拥有 std容器实际上没有合理的用例。容器是轻量级的(当它们没有任何东西时)并且便宜地可移动。您可以考虑用例,但我可以保证,通过将容器封装在另一个堆分配的对象中,可以更好地完成您打算做的任何事情。在精心设计的代码中,这应该永远不会成为一个问题。无论如何,从priority_queue模板类私下继承可以消除这种风险。

      1. 切片
      2. 当我们传递继承的对象时,当然存在切片的风险。这里的答案是从priority_queue基类私下继承,然后在派生类中使用using来仅导出我们希望共享的基类接口的部分。

        以下示例已更新以显示此内容。

        下面是一个真实项目的例子。

        要求: 保留必须通知客户端的主题队列。按照通知此主题的最早时间的时间戳排序队列。不允许重复的主题名称。

        这是一个有效的演示:

        #include <queue>
        #include <string>
        #include <chrono>
        #include <cassert>
        #include <iostream>
        
        using topic_type = std::string;
        using timestamp_clock = std::chrono::system_clock;
        using timestamp_type = timestamp_clock::time_point;
        
        struct notification {
            topic_type topic;
            timestamp_type timestamp;
        };
        
        bool topic_equals(const notification& l, const topic_type& r) {
            return l.topic == r;
        }
        bool topic_equals(const topic_type& l, const notification& r) {
            return l == r.topic;
        }
        
        bool age_after(const notification& l , const notification& r) {
            return l.timestamp > r.timestamp;
        }
        
        bool age_after(const notification& l , const timestamp_type& r) {
            return l.timestamp > r;
        }
        
        bool age_after(const timestamp_type& l , const notification& r) {
            return l > r.timestamp;
        }
        
        struct greater_age
        {
            template<class T, class U>
            bool operator()(const T& l, const U& r) const {
                return age_after(l, r);
            }
        };
        
        template<class T>
        struct pending_queue_traits;
        
        template<>
        struct pending_queue_traits<notification>
        {
            using container_type = std::vector<notification>;
            using predicate_type = greater_age;
            using type = std::priority_queue<notification, container_type, predicate_type>;
        };
        
        class pending_notification_queue
        : private pending_queue_traits<notification>::type
        {
            using traits_type = pending_queue_traits<notification>;
            using base_class = traits_type::type;
        
        public:
        
        
            // export the constructor
            using base_class::base_class;
        
            // and any other members our clients will need
            using base_class::top;
            using base_class::pop;
            using base_class::size;
        
            bool conditional_add(topic_type topic, timestamp_type timestamp = timestamp_clock::now())
            {
                auto same_topic = [&topic](auto& x) { return topic_equals(topic, x); };
                auto i = std::find_if(std::begin(c), std::end(c), same_topic);
                if (i == std::end(c)) {
                    this->push(notification{std::move(topic), std::move(timestamp)});
                    return true;
                }
                else {
                    if (timestamp < i->timestamp) {
                        i->timestamp = std::move(timestamp);
                        reorder();
                        return true;
                    }
                }
                return false;
            }
        
        private:
            void reorder() {
                std::make_heap(std::begin(c), std::end(c), comp);
            }
        };
        
        // attempt to steal only the base class...
        void try_to_slice(pending_queue_traits<notification>::type naughty_slice)
        {
            // danger lurks here
        }
        int main()
        {
            using namespace std::literals;
        
            auto pn = pending_notification_queue();
        
            auto now = timestamp_clock::now();
            pn.conditional_add("bar.baz", now);
            pn.conditional_add("foo.bar", now + 5ms);
            pn.conditional_add("foo.bar", now + 10ms);
            pn.conditional_add("foo.bar", now - 10ms);
        
            // assert that there are only 2 notifications
            assert(pn.size() == 2);
            assert(pn.top().topic == "foo.bar");
            pn.pop();
            assert(pn.top().topic == "bar.baz");
            pn.pop();
        
            // try to slice the container. these expressions won't compile.
        //    try_to_slice(pn);
        //    try_to_slice(std::move(pn));
        
        }
        

答案 4 :(得分:1)

stl的容器无法提供尽可能快的可更新优先级队列。

@Richard Hodges:make_heap的复杂度为O(n),而push_heap函数不会告诉您所提供元素的存储位置,因此无法快速更新单个元素(您需要需要O(n)来找到它。

我已经实现了高性能的可更新优先级队列(更新成本为O(log n),是使用std::set的两倍,并使其可用on github

这是通常的使用方式:

better_priority_queue::updatable_priority_queue<int,int> pQ;
pQ.push(0, 30);   // or pQ.set(0, 30)
pQ.push(1, 20);
pQ.push(2, 10);

pQ.update(2, 25); // or pQ.set(2, 25)

while(!pQ.empty())
    std::cout << pQ.pop_value().key << ' ';

// Outputs: 0 2 1