优先队列
1、什么是优先队列
能够完成下列两种操作的数据结构,我们便称之为优先队列。
①插入一个数值 ②取出最大(或者最小)的数值(获取数值,并且删除)。
从严格意义上来说优先队列,并不是队列,因为它并不遵循队列的FIFO(先进先出的原则)。
2、实现优先队列
我们可以使用一种叫做“堆(heap)”的数据结构来实现优先队列。堆有一个重要的性质就是儿子的值一定不小于父亲。除此之外,树的节点是从上到下、从左到右的顺序紧凑排列的。堆就是如下图的二叉树
我们向堆插入数值时,首先我们先在堆的尾部插入该值,然后再根据大小关系不断的提升它的位置
删除堆的最小值时,首先把堆的最后一个节点复制到根节点上,然后删除最后一个节点。之后我们根据大小关系不断和交换位置,使其满足堆的定义。
堆的这两种操作所用的时间,和树的深度成正比。我们不难发现堆的时间复杂度为O(log n).
现在我们就可以来实现堆了,为了简单一些我们使用数组来实现:
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 5 const int max_N = 20; 6 int heap_size = 0; 7 int heap[max_N]; 8 9 void push(int x){ 10 heap_size++; 11 heap[heap_size - 1] = x; 12 int i = heap_size - 1; 13 while(i > 0){ 14 int p = (i - 1) / 2; 15 if(heap[p] <= x) 16 break; 17 heap[i] = heap[p]; 18 i = p; 19 } 20 heap[i] = x; 21 } 22 23 int top(){ 24 int ret = heap[0]; 25 int x = heap[heap_size - 1]; 26 heap_size--; 27 int i = 0; 28 while(2 * i + 2 <= heap_size){ 29 int a = 2 * i + 1, b = 2 * i + 2; 30 if(heap[a] > heap[b] && b < heap_size) 31 a = b; 32 if(x <= heap[a]) 33 break; 34 heap[i] = heap[a]; 35 i = a; 36 } 37 heap[i] = x; 38 return ret; 39 } 40 41 int main(){ 42 int tmp; 43 while(cin >> tmp){ 44 push(tmp); 45 } 46 while(heap_size > 0){ 47 cout << top() << " "; 48 } 49 return 0; 50 }
3、STL之priority_queue
然而很多时候我们并不需要自己实现堆。C++为我们提供了模板类priority_queue。STL的priority_queue包含在头文件queue中, 由于priority_queue使用堆实现,所以我们可以知道priority_queue的时间复杂度应该也为 O(log n)。不过和上述堆实现的优先队列有些许不同。因为priority_queue取出数值时是最大值。我们来看看priority_queue的用法。
<1>priority_queue的基本操作
priority_queue我们常用的有四个成员函数分别
bool empty() const; | 返回值为true,则该优先队列为空,反之亦然 |
size_type size() const; | 返回优先队列中元素的数量,size_type是unsigned integral type |
void pop(); | 删除队列顶部的元素,也就是根节点 |
void push (const value_type& val); | 将元素加入,优先队列中 |
const_reference top() const; | 返回队列顶部的元素,const_reference为队列顶部的类型 |
<2>改变priority_queue中的排列顺序
在很多时候,我们需要的不一定是最大值,也有可能是最小值。这是就需要我们来改变priority_queue中的顺序。方法有两种:
①如果加入优先队列的是基本类型,那么我们就可以这样,我们以int为例:
//注意greater<int> >这之间有一个空格
priority_queue<int, vector<int>, greater<int> >Q;
②对于自定义数据类型的话,我们不论是要改变排序方式,还是不改变都要这样 -- 重载 小于( < ) 运算符:
因为,如果你不重载比较运算符的话,编译器无法比较自定义数据类型的大小关系。然而又因为在priority_queue的内部,只需用到 小于号(<),所以我们只需要重载小于号即可。当然对于自定义数据类型来说,也是必须重载,否则将无法使用priority_queue。重载小于号,我们可以有两种方式,一种用成员函数,一种使用友元函数(这里就不多说了,不会的同学,自己在好好复习复习C++)。
//定义结构,使用运算符重载,自定义优先级1 struct cmp1{ bool operator ()(int &a,int &b){ return a>b;//最小值优先 } }; struct cmp2{ bool operator ()(int &a,int &b){ return a<b;//最大值优先 } };
注意:如果使用成员函数重载小于号的话,那么要将重载函数变为常成员函数,否则将无法通过编译。
优先队列(堆)的主要作用就是两个操作:插入,删除最小。之所以要重新引入这个概念,就是为了能够利用较小的时间复杂度来完成。
应用:寻找第k个最小值
可以先对数组用BuildHeap操作,然后执行k次DeleteMin。