文章出处
牛客网-wayneYM
一文讲明白优先级队列为啥按less排序却是从大到小
写在前面: 如果你一直纠结为啥自定义明明是greater<>但是出堆却是从小到大,看完这篇文章你就懂了!
优先级队列 Priority_queue
这是一个拥有权值queue,其内部元素按照元素的权值排列。权值较高者排在最前优先出队。其中缺省情况下系统是通过一个max-heap以堆实现完成排序特性,表现为一个以vector表现的完全二叉树。
定义
priority_queue<Type, Container, Functional>
其中Type代表数据类型,Container代表容器类型,缺省状态为vector; Function是比较方式,默认采用的是大顶堆(less<>)。
//升序队列 小顶堆 great 小到大
priority_queue <int,vector,greater> pq;
//降序队列 大顶堆 less 大到小 默认
priority_queue <int,vector,less> pq;
包含的方法
- top() 访问队头
- empty()
- size()
- push() / emplace
- pop
- swap
如何自定义比较函数(主要讲仿函数已了解可跳过)
利用std比较函数的实例
std自带**_greater_和_less_两个比较方法。他们实现过程使用的是仿函数**和类模板。
仿函数的理解
greater和less是std实现的两个仿函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
template < class T > struct greater { bool operator()(const T & x, const T & y) const { return x > y; } typedef T first_argument_type; typedef T second_argument_type; typedef bool result_type; };
template < class T > struct less { bool operator()(const T & x, const T & y) const { return x < y; } typedef T first_argument_type; typedef T second_argument_type; typedef bool result_type; };
|
operator()的重载
这个可以重载括号运算符,与==,<,>这类运算符相同。
案例:
1 2 3 4 5 6 7 8 9 10 11
| class test { void operator()(int x) { cout << x << endl; } } int main() { test t; t(10); return 0; }
|
仿函数
仿函数就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
- 仿函数是一个类,不是函数
- 仿函数重载了(),使得可以类似调用函数那样调用实例。(所以大小堆的调用是greater() ,就是类似调用函数的,实际上是一个叫greater的模板类,输入的参数类型是int,()是这个模板类的一个函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| const int CMP_LES = -1; const int CMP_EQU = 0; const int CMP_BIG = 1; class Comparer { public: Comparer(int cmpType) { m_cmpType = cmpType; } bool operator()(int num1, int num2) const { bool res; switch (m_cmpType) { case CMP_LES: res = num1 < num2; break; case CMP_EQU: res = num1 == num2; break; case CMP_BIG: res = num1 > num2; break; default: res = false; break; } return res; } private: int m_cmpType; };
void Swap(int & num1, int & num2) { int temp = num1; num1 = num2; num2 = temp; } void SortArray(int array[], int size, const Comparer & cmp) { for (int i = 0; i < size - 1; ++i) { int indx = i; for (int j = i + 1; j < size; ++j) { if (cmp(array[indx], array[j])) { indx = j; } } if (indx != i) { Swap(array, array[indx]); } } } void ListArray(int array[], int size) { for (int i = 0; i < size; ++i) { cout << array << " "; } }
#define ARY_SIZE 10 int main() { int array[ARY_SIZE] = { 10, 12, 9, 31, 93, 34, 98, 9, 1, 20 }; cout << "The initial array is : "; ListArray(array, ARY_SIZE); cout << endl; SortArray(array, ARY_SIZE, Comparer(CMP_BIG)); cout << "The ascending sorted array is :"; ListArray(array, ARY_SIZE); cout << endl; SortArray(array, ARY_SIZE, Comparer(CMP_LES)); cout << "The descending sorted array is : "; ListArray(array, ARY_SIZE); cout << endl; return 0; }
|
运行结果:
The initial array is : 10 12 9 31 93 34 98 9 1 20
The ascending sorted array is :1 9 9 10 12 20 31 34 93 98
The descending sorted array is : 98 93 34 31 20 12 10 9 9 1
49行处虽然cmp是一个类,但是用法像一个函数,即仿函数的意义
greater和less的代码实现
greater定义如下
template <class T> struct greater{
bool operate() (const T& x, const T &y) const{return x>y;}
typedef T first_argument_type;
typedef T second_argument_type;
typedef bool result_type;
}
less定义如下
template <class T> struct less {
bool operator() (const T& x, const T& y) const {return x<y;}
typedef T first_argument_type;
typedef T second_argument_type;
typedef bool result_type;
};
常见实例
//升序队列 小顶堆 great 小到大
priority_queue <int,vector,greater> pq;//升序
//降序队列 大顶堆 less 大到小 默认
priority_queue <int,vector,less> pq;//降序
实际上是保持优先级最高的元素在[0]的位置,每次pop或者push操作会更新
声明参数
priority_queue<Type, Container, Funcitonal>;
priority_queue<pair<int,int> > pq_pair;//结果先按照pair的first元素降序,first元素相等再按照second元素降序
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >
!!sort和priority_queue为什么顺序不同
可以发现对于sort和priority_queue,使用greater和less类模板是结果不同的。
主要原因是因为priority_queue的内部实现方法是堆,less对应的是大顶堆。在此排序下调用top()得到的是堆顶,也就是取值时是从大到小。push对应的底层函数是push_heap(),每次添加元素入堆时,在默认情况下添加进去的数据作为较小值入堆。
顺序真正不同的根本原因
堆的内部实现是vector,每次出堆的时候 实际上是堆顶元素和最后一个元素互换(把最大元素沉到数组末端)。那么实际上假如没有出堆这一个过程而是继续将数据缓存在vector中,所有元素出堆后,形成的数组就是升序的。只是由于出堆这一过程导致了元素出堆反序了,相当于逆序输出。
一个例子
例如排好的大顶堆[9,5,7,3]。(9是根节点)
第一次出堆的时候9会被移到最后与3交换位置,同时会对3进行下沉保证堆结构,9出堆。
[3,5,7,9]->[7,5,3,|9] (|后的数字代表已经出堆)
第二次出堆 7与3交换位置。( 此时9已经出堆了,只是为了方便后续表达所以保留)
[3,5,|7,9]->[5,3,|7,9]
第三次出堆 5与3交换位置。5出堆
[5,3,|7,9]->[3,|5,7,9]
第四次出堆 3出堆,此时堆为空
[|3,5,7,9]
可以观察到,假如我们将出堆的数保留在vector中的话,数字排序确实是递增的(符合less的直观性质),但是由于出堆的问题,顺序刚好反了过来,所以(less对应的是大顶堆,出堆顺序是从大到小)
- n个无序元素构成大顶堆(最后一个节点上浮)
- 根节点和最后一个元素交换
- 剩下n-1个元素重新构成大顶堆(根节点下沉)
- 重复2,3直到数组排序完毕
//默认都是less
sort(vec.begin(),vec.end(),less<int>()); //内置类型从小到大 升序
priority_queue <int,vector<int>,less<int> > pql; //top出数据从大到小 降序
sort(vec.begin(),vec.end(),greater<int>()); //内置类型从大到小 降序
priority_queue <int,vector<int>,greater<int> > pqg; //top出数据从小到大 升序
| 方法或容器 | less()的顺序(默认) | greater()的顺序 | cmp函数 > |
|---|
| priority_queue 全是反过来的 | 降序 大->小 | 升序 小->大 | 升序 小->大 |
| sort | 升序 小->大 | 降序 大->小 | |
| map | 升序 小->大 | 降序 大->小 | |
| map | 升序 小->大 | 降序 大->小 | |
除了priority_queue使用的是堆,导致全部大小比较反了过来,其他均是正常符合逻辑的操作,即判断为**_func(a,b)_判断为true则a在前**。只有priority_queue特殊,如果func(a,b)判断为true,优先级队列中b在前。
对比一下的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <algorithm> #include <iostream> #include <queue> #include<iterator> using namespace std; int main(){ int A[]={1,4,3,7,10}; const int N=sizeof(A)/sizeof(int); vector<int> vec(A,A+N); ostream_iterator<int> output(cout," "); cout<<"Vector vec contains:"; copy(vec.begin(),vec.end(),output); cout<<"\nAfter greater<int>():"; sort(vec.begin(),vec.end(),greater<int>()); copy(vec.begin(),vec.end(),output); cout << endl; priority_queue <int,vector<int>,greater<int> > pqg; for(auto i : vec) pqg.push(i); while(!pqg.empty()){ cout << pqg.top() << ' '; pqg.pop(); } cout << endl; cout<<"\nAfter less<int>():"; sort(vec.begin(),vec.end(),less<int>()); copy(vec.begin(),vec.end(),output); cout << endl; priority_queue <int,vector<int>,less<int> > pql; for(auto i : vec) pql.push(i); while(!pql.empty()){ cout << pql.top() << ' '; pql.pop(); } cout << endl;
return 0; }
|
自定义优先级队列实例
希望排序结果从小到大,(对应great的小顶堆排序方式)
!注意,重写的是仿函数不是函数,cmp应该是一个类或者struct
注意堆排序导致顺序反了过来,如果cmp(a,b) == true在堆中的排序说明b会在a前面,那么要从小到大,应该用>符号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| struct Node{ int x,y; Node(int a = 0 ,int b = 0): x(a),y(b){} }; struct cmp{ bool operator() (Node a,Node b){ if(a.x == b.x) return a.y>b.y; return a.x>b.x; } }; int main(){ priority_queue<Node, vector<Node>, cmp> pq; for(int i = 0; i < 10 ; ++i){ pq.push(Node(rand(),rand())); } while(!pq.empty()){ cout<<pq.top().x<<' '<<pq.top().y<<endl; pq.pop(); } return 0; }
|
希望从大到小排序,对应less()的排序顺序
并且利用系统自带的仿函数greater或者less。其中greater使用的是>符号,le***的是<符号。了解原理后实际上重载<或>`均可。
希望通过less实现,由于less()的实现借助了<,所以可以通过,重写<符号实现
需要实现的是less(a,b) = true, b会放在前面,此处要实现大的在前,所以要实现less<T>(小数,大数) = true,保留<号原有意义即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct Node{ int x,y; Node(int a = 0 ,int b = 0): x(a),y(b){} };
bool operator<(Node a ,Node b){ if(a.x==b.y) return a.y<b.y; return a.x<b.x; }
int main(){ priority_queue<Node> pq; return 0; }
|
反之如果希望借助less()实现从小到大排序,则第7 8 行的比较符号应该为>符号。
例题
406. 根据身高重建队列
仿函数cmp类体现了priority_queue的用法和注意事项。
思路全在代码中了,题目本身较容易想出解法,主要在于实现思路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| struct Node{ int height,overh; Node():height(0), overh(0){} Node(int _x, int _y): height(_x), overh(_y) {} };
struct cmp{ bool operator() (vector<int> x, vector<int> y){ if(x[1]==y[1]) return x[0] < y[0]; return x[1] > y[1]; } };
class Solution { public: vector<vector<int>> reconstructQueue(vector<vector<int>>& people) { priority_queue<vector<int>, vector<vector<int> > ,cmp> pq; for(auto p : people) pq.push(p); vector< vector<int> > ans; int over = 0,pos=0; while(!pq.empty()){ auto cur = pq.top(); pq.pop(); over = 0,pos =0; while( over < cur[1] ){ if( ans[pos][0] >= cur[0] ) over++; pos++; } ans.insert(ans.begin()+pos, cur); } return ans; } };
|