1.priority_queue(优先级队列)
优先级队列的底层是堆,每次队首元素都是优先级最大的。
头文件
include<queue>
using namespace std;
常用操作
- push()
push(x)令x入队,复杂度O(logN)
- top()
获取队首元素,复杂度O(1)
- pop()
令队首元素出队,复杂度O(logN)
- empty()
检测优先队列是否为空,复杂度O(1)
- size()
返回优先队列内元素个数,复杂度O(1)
优先级设置
(1)基本数据类型的优先级设置
priority_queue q == priority_queue<int,vector, less > q;
上面两种表述方式作用相同,其中vector表示承载优先级队列的容器, less表示数字大的优先级越大,进一步理解可以是less重栽了小于号运算(注意只能重载小于号运算!!).
//优先级队列优先级设置
#include <iostream>
#include <queue>
using namespace std;
int main(){
//---1---基本数据类型:设置优先级队列总是把最小的元素放在队首
priority_queue<int,vector<int>,greater<int> > q;//注意<int> >之间的空格
q.push(3);
q.push(4);
q.push(1);
printf("%d\n",q.top());
return 0;
}
(2)结构体的优先级设置
- 方式一: 小于号重载在结构体内部
//结构体优先级设置
#include <iostream>
#include <queue>
using namespace std;
struct fruit{
string name;
int price;
friend bool operator < (fruit f1,fruit f2){
return f1.price < f2.price;
}
}f1,f2,f3;
int main(){
priority_queue<fruit> q;
f1.name = "桃子";
f1.price = 3;
f2.name = "梨子";
f2.price = 4;
f3.name = "苹果";
f3.price = 1;
q.push(f1);
q.push(f2);
q.push(f3);
cout<<q.top().name<<" "<<q.top().price<<endl;
return 0;
}
- 方式二: 小于号重载在结构体外部
#include <iostream>
#include <queue>
using namespace std;
struct fruit{
string name;
int price;
friend bool operator < (fruit f1,fruit f2){
return f1.price < f2.price;
}
}f1,f2,f3;
struct cmp{
bool operator () (fruit f1,fruit f2){
return f1.price > f2.price;
}
};
int main(){
//重载优先级结构体进行排序
priority_queue<fruit> q;
f1.name = "桃子";
f1.price = 3;
f2.name = "梨子";
f2.price = 4;
f3.name = "苹果";
f3.price = 1;
q.push(f1);
q.push(f2);
q.push(f3);
cout<<q.top().name<<" "<<q.top().price<<endl;
//用cmp函数优先级排序
priority_queue<fruit,vector<fruit>,cmp> qq;
f1.name = "桃子";
f1.price = 3;
f2.name = "梨子";
f2.price = 4;
f3.name = "苹果";
f3.price = 1;
qq.push(f1);
qq.push(f2);
qq.push(f3);
cout<<qq.top().name<<" "<<qq.top().price<<endl;
return 0;
}
应用练习
2.堆的定义
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子结点的值。
大顶堆:父节点值>=孩子结点的值 此时父节点的值为子树最大
小顶堆:父节点值<=孩子结点的值 此时父节点的值为子树最小
堆是完全二叉树,一般用数组来存储
const int maxn=100;
//heap数组存储堆 n为实际数组元素个数
int heap[maxn],n=10;
基本操作
向下调整(O(logn))
//单个结点的向下调整 O(logn)
//对heap下标为low的结点进行调整 high为数组中最后一个元素下标
void downAdjust(int low,int high){
int i=low,j=low*2;//i为欲调整的下标 j为左孩子
while(j<high){//有左孩子
//注意 先判断j+1<high
if(j+1<high&&heap[j+1]>heap[j]) j++;//右孩子更大时j为右孩子下标
if(heap[j]>heap[i]){
swap(heap[i],heap[j]);//调整父子关系
i=j; //调整后可能还需要调整 处理i,j了
j=i*2; //注意每次初始i是欲调整的结点下标 而j是左孩子
}else{
//根就是最大的 直接break,不用调了
break;//return也行 但是不能没有else分支 否则死循环
}
}
}
向上调整(O(logn))
//插入结点
//向上调整
//对heap数组在[low,high]范围内进行向上调整
//low一般置为1 即整棵树的根,high为欲向上调整的结点下标
void upAdjust(int low,int high){
int i=high,j=i/2;//i为欲调整下标 j为父亲结点
while(j>=low){//父亲在[low,j]范围内 low为1时就整棵树前面的范围
if(heap[j]<heap[i])
swap(heap[i],heap[j]);
i=j;
j=i/2;
}else{
break;
}
}
}
建堆(O(n))
//建堆 一开始已经数组heap中已经有了初始序列 建堆只是调整一下数组中元素的位置而已
void CreateHeap(){
//从下往上 第一个非叶子结点开始 向上调整
//n为堆元素个数 全局变量
for(int i=n/2;i>=1;i--){
downAdjust(i,n);
}
}
删除结点(O(logn))
//删堆
//删除堆中一个结点 也要考虑调整问题,但是删除后该位置空缺如何调整呢?
//给出的策略是:最后一个元素来填空 然后n-- 然后向下调整被删结点位置即可
void DeleteHeap(int i){
heap[i]=heap[n--];
downAdjust(i,n);
}
插入结点(O(logn))
//插入结点
void insert(int x){
heap[++n]=x;
upAdjust(1,n);
}
堆排序(O(nlogn))
void heapSort(){
CreateHeap();//先要建立堆 即对非叶子结点都先分别做一次向下调整
for(int i=n;i>1;i--){
swap(heap[1],heap[i]);
downAdjust(1,i-1);//heap[1]在i-1的范围内向下调整即可
}
}
应用练习
堆排序一般要从下标为1的数组开始