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

关于优先队列\(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;
posted @ 2022-10-19 14:43  糖豆爸爸  阅读(374)  评论(1)    收藏  举报
Live2D