关于优先队列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>
可以满足建立大根堆或者小根堆。
结构体
对于结构体而言,将结构体放入优先队列中,比较函数需要建立在针对结构体的具体成员。
参考https://www.cnblogs.com/flipped/p/5691430.html 博客自定义优先级。
自定义优先级的三种方法:
1.<
以成员函数重载:
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中默认的比较函数为less,less函数中只用到了 { 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; } };
2.自定义比较函数:
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,这样分析同上。
3.<
以类成员函数重载
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
4.<
以友元函数重载
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;
刚开始不太明白友元操作符重载函数,先了解下重载与友元。
重载类型
参考:https://www.cnblogs.com/Mayfly-nymph/p/9034936.html
综合,在C++中,操作符重载实现通过类成员函数、全局函数(非类成员函数)与友元函数(不是类成员却能够访问类的所有成员 )。
上面对<
重载使用到类全局函数、友元函数、类成员函数。
使用友元函数重载有两个优点:和普通函数重载相比,它能够访问类的非公有成员与将双目运算符重载为友元函数,这样就可以使用交换律。
交换律可以理解成复数加法中,以成员函数的形式重载 +,只能计算 c + 15.6
,不能计算 28.23 + c
,这是不对称的 。
何为友元
友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。
尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;
友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。如下设置友元函数:
class Box { double width; public: double length; friend void printWidth( Box box ); void setWidth( double wid ); };
声明类 Usb 的所有成员函数作为类 Phone 的友元,需要在类 Phone 的定义中放置如下声明:
class Usb { private: int size; friend class Phone; //声明 Phone为友元类 }; class Phone { public: Usb usb; void setPhone()
{ usb.size += 1000; //因Phone是Usb的友元类,故此处可以访问其私有成员 } };