內部實現
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的友元類,故此處可以訪問其私有成員 } };