You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
python/TangDou/AcWing/MiniPath/关于优先队列priority_queue大小根堆、重载...

7.7 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

关于优先队列priority\_queue大小根堆、重载操作符的说明

感谢原作者

一、关于priority\_queue的说明

内部实现

priority_queue默认情况下,以vector为底层容器,加上heap(默认max-heap) 处理规则;形成大根堆

priority\_queue被归为 container adapter,也就是对 container 进行封装一层。

priority_queue操作规则上是 queue,只允许在尾部加入元素,并从首部取出元素;只不过内部元素具有优先级,优先级高者先出。

priority_queue的所有元素进出具有一定规则,所以 不提供遍历功能,也不提供迭代器。

疑惑产生

下面为priority_queue的使用规则,第一个传入了类型,第二个为容器类型,第三个为比较函数

template<
    class T,
    class Container = std::vector<T>,
    class Compare = std::less<typename Container::value_type>   //comp默认为less
> class priority_queue;

疑惑关键就在于比较函数。

priority_queue默认形成大根堆,而传入的comp默认为less

为何传入一个可将序列顺序调为有小到大的函数,建成的堆反而是大顶堆呢?

不知你们有没有这种感觉?直觉上认为传入less,建成小顶堆,而传入greater,建成大顶堆。

源码解析

std::less()源码:若__x < __y,则返回true,顺序不变,否则,顺序发生变化。这个函数名含义与实现效果相一致。

struct less : public binary_function<_Tp, _Tp, bool>
{
    _GLIBCXX14_CONSTEXPR
    bool
    operator()(const _Tp& __x, const _Tp& __y) const
    { return __x < __y; }
};

make_heap中调用的__push_heap源码:

__push_heap(_RandomAccessIterator __first, _Distance __holeIndex,
            _Distance __topIndex, _Tp __value, _Compare __comp)
    //__holeIndex  新添加节点的索引,即叫做孔洞
    //__topIndex  顶端索引
    //__value   新添加节点的值
    //__comp    比较函数传入为less
{
  _Distance __parent = (__holeIndex - 1) / 2;   //找到新节点父节点索引
  while (__holeIndex > __topIndex && __comp(*(__first + __parent), __value)) {
      //若孔洞没有到达最顶端  &&  父节点的值小于新添加节点的值,则要进行下列操作
      //less中左边比右边小则返回true与less愿意相同
    *(__first + __holeIndex) = *(__first + __parent);   //将父节点的值放入孔洞
    __holeIndex = __parent; //孔洞的索引编程了原来父节点的索引,往上移动了
    __parent = (__holeIndex - 1) / 2;   //那么孔洞的父节点又要继续往上找
  }
  *(__first + __holeIndex) = __value; //将新添加节点的值放在找到的位置(孔洞)
}

经过上面源码分析,传入的comp就是为了在比较 孔洞节点父节点 的大小,若返回true 才会进行交换操作

所以传入less,返回true是因为 父节点比孔洞节点小,所以要进行交换,则将大的值移动到前面,所以建成的堆为 大顶堆

而传入greater,返回true是因为 父节点孔洞节点大,所以进行交换,则将大的值移动到了后面,所以建成的堆为 小顶堆

所以,就明白了为什么传入less反而形成了大根堆,而传入greater则形成了小根堆。

二、如何使用

#include <queue>
using namespace std;

priority_queue<int> que;    //默认定义了最大堆等同于将第三个参数使用less<int>
priority_queue<int, vector<int>, less<int>> que;  //定义大根堆

priority_queue<int, vector<int>, greater<int>> que;  //定义小根堆VS下需要加入头文件#include<functional>

//测试 priority_queue<int> que;
que.push(3);
que.push(5);
que.push(4);
cout << que.top() << endl;  //5

//测试 priority_queue<int, vector<int>, less<int>> que;
que.push(3);
que.push(5);
que.push(4);
cout << que.top() << endl;  //5

//测试 priority_queue<int, vector<int>, greater<int>> que;
que.push(3);
que.push(5);
que.push(4);
cout << que.top() << endl;  //3

三、关于自定义优先级

上面给出了在处理整型等基本数据类型时直接出入less或者greater既可以建立相应的大顶堆或者小顶堆。若是处理结构体呢?如何定义优先级呢?

普通数据类型

基本数据类型的比较函数可以直接使用less<int>或者greater<int>可以满足建立大根堆或者小根堆。

结构体

对于结构体而言,将结构体放入优先队列中,比较函数需要建立在针对结构体的具体成员。

自定义优先级的四种方法:

  • <以成员函数重载
struct Node {   //我们将Node节点放入优先队列中希望以value进行比较
    Node(int _id, int _value) : id(_id), value(_value){}
    int id;
    int value;
};

//大根堆
bool operator < (const Node& a, const Node& b){
    return a.value < b.value; //将value的值由大到小排列形成Node的大根堆
}

int main() {  
    struct Node node1(1, 5);
    struct Node node2(2, 3);
    struct Node node3(3, 4);
    
    priority_queue<Node> que;
    
    que.push(node1);
    que.push(node2);
    que.push(node3);
    
    cout << que.top().value << endl;    //5
}

//小根堆
bool operator < (const Node& a, const Node& b)
{
    return a.value > b.value; //将value的值由小到大排列形成Node的小根堆
}

cout << que.top().value << endl;    //3

我试了 bool operator > (),结果会报二进制“<”: 没有找到接受const Node类型的左操作数的运算符(或没有可接受的转换) 错误,我想原因应该是这样吧:priority_queue中默认的比较函数为lessless函数中只用到了 { return __x < __y; },所以重载中若只重载了>,函数找不到<,所以会出现错误。

struct less : public binary_function<_Tp, _Tp, bool>{
    _GLIBCXX14_CONSTEXPR
    bool
    operator()(const _Tp& __x, const _Tp& __y) const
    { return __x < __y; }
};
  • 自定义比较函数
struct cmp{
    bool operator ()(const Node& a, const Node& b){
        return a.value < b.value;//将value的值由大到小排列形成Node的大根堆
    }
};
priority_queue<Node, vector<Node>, cmp>q;
cout << que.top().value << endl;    //5

struct cmp{
    bool operator ()(const Node& a, const Node& b){
        return a.value > b.value;//将value的值由小到大排列形成Node的小根堆
    }
};
priority_queue<Node, vector<Node>, cmp>q;
cout << que.top().value << endl;    //3

上述在传入用户自定义的比较函数,那么在建堆过程中使用的comp函数即为我们自定义的cmp,这样分析同上。

  • < 以类成员函数重载
struct Node {    //我们将Node节点放入优先队列中希望以value进行比较
    Node(int _id, int _value) : id(_id), value(_value){}
    int id;
    int value;
    //大根堆
    bool operator < (const Node& b) const    //注意此处若没有const则会报错
    {
        return value < b.value; //将value的值由大到小排列形成Node的大根堆
    }
};
cout << que.top().value << endl;  //5
  • <以友元函数重载 【这个和竞赛关系不大,暂时不用考虑】
struct Node{
    int id;
    int value;
    friend bool operator<(const Node& a,const Node& b){
        return a.value<b.value;  //按value从大到小排列
    }
};
priority_queue<Node>q;