堆
(二叉)堆是一个数组,可以被看成近似的完全二叉树
父节点/左孩子/右孩子
int parent(int i){ return i/2; } int left(int i){ return 2*i; } int right(int i){ return 2*i+1; }
二叉堆可以分为最大堆和最小堆
最大堆:某个节点至多与其父节点一样大
最小堆:除了根以外的所有节点i,都有A[parent(i)]<=A[i]
堆排序算法我们可以使用最大堆,最小堆通常用于构造优先队列
堆的高度定义为根节点的高度(从下往上数0,1,2...)
堆结构的一些基本操作的运行时间至多与树的高度成正比,即O(logn)
维护最大堆--O(logn)
void max_heapify(int A[],int i,int length){//数组传参不用带引用 int l = left(i); int r = right(i); int largest; if(l<=length && A[l]>A[i]){ largest = l; }else{ largest = i; } if(r<=length && A[r]>A[largest]){ largest = r; } if(largest != i){ swap(A[i],A[largest]); max_heapify(A,largest,length); } }
构造堆--O(n)
void build_max_heap(int A[],int length){//这里下标是以1开始的 for(int i=length/2;i>=1;i--){ max_heapify(A,i,length); } }
堆排序--O(nlogn)
void heapsort(int A[],int length){//从小到大的堆排序 build_max_heap(A,length); for(int i=length;i>=2;i--){ swap(A[1],A[i]); length = length-1; max_heapify(A,1,length); } }
最小堆
void min_heapify(vector<pair<int,int> > &A,int i,int length){//数组传参不用带引用 int l = left(i)+1; int r = right(i)+1; int shortest; if(l<=length && A[l].first<A[i].first){ shortest = l; }else{ shortest = i; } if(r<=length && A[r].first<A[shortest].first){ shortest = r; } if(shortest != i){ swap(A[i],A[shortest]); min_heapify(A,shortest,length); } } //这里下标是以0开始的 void build_min_heap(vector<pair<int,int> > &A,int length){ for(int i=length/2-1;i>=0;i--){ min_heapify(A,i,length); } }
优先队列的一些操作--O(logn)
1. Insert - (push)
堆的插入就是把新的元素放到堆底,然后检查它是否符合堆的性质,如果符合就丢在那里了,如果不符合,那就和它的父亲交换一下,一直交换交换交换,直到符合堆的性质,那么就插入完成了
void swap(int &x,int &y){int t=x;x=y;y=t;}//交换函数 int heap[N];//定义一个数组来存堆 int siz;//堆的大小 void push(int x){//要插入的数 heap[++siz]=x; now=siz; //插入到堆底 while(now){//还没到根节点,还能交换 ll nxt=now>>1;//找到它的父亲 if(heap[nxt]>heap[now])swap(heap[nxt],heap[now]);//父亲比它大,那就交换 else break;//如果比它父亲小,那就代表着插入完成了 now=nxt;//交换 } return; }
2.Minimun
3.Extract-min
4.Decrease-key
5.Delete - (pop)
删除操作,但是在实际的代码操作中,并不是这样进行删除操作的,有一定的微调,代码中是直接把堆顶和堆底交换一下,然后把交换后的堆顶不断与它的子节点交换,直到这个堆重新符合堆性质
void pop(){ swap(heap[siz],heap[1]);siz--;//交换堆顶和堆底,然后直接弹掉堆底 int now=1; while((now<<1)<=siz){//对该节点进行向下交换的操作 int nxt=now<<1;//找出当前节点的左儿子 if(nxt+1<=siz&&heap[nxt+1]<heap[nxt])nxt++;//看看是要左儿子还是右儿子跟它换 if(heap[nxt]<heap[now])swap(heap[now],heap[nxt]);//如果不符合堆性质就换 else break;//否则就完成了 now=nxt;//往下一层继续向下交换 } }
优先队列(堆)的STL实现
如果开了O2优化能使程序的编译效率大大提升
注意堆里面的大小,并非传统意义下的大小
定义一个优先队列:
首先你需要一个头文件:#include<queue> priority_queue<int> q;//这是一个大根堆q priority_queue<int,vector<int>,greater<int> >q;//这是一个小根堆q
优先队列的操作:
q.top()//取得堆顶元素,并不会弹出 q.pop()//弹出堆顶元素 q.push()//往堆里面插入一个元素 q.empty()//查询堆是否为空,为空则返回1否则返回0 q.size()//查询堆内元素数量
额外提一下重载在堆的使用
bool operator < (node a , node b) { return a.w > b.w; }
熟悉优先队列STL的人都知道,priority_queue默认大根堆,但如果我们想把他直接用去优化dijkstra肯定是不行的,所以我们可以以上的一段代码加入来将priority_queue变为小根堆,定义优先队列这样写priority_queue <node> q就行了</node>
当然,你也可以将重载运算符写在sruct里:
struct node { int id , w; bool operator < (const node &b) const { return w > b.w; } }
如果你不使用重载运算符的话,就得这么写:
struct node { int id , w; } struct cmp { bool operator ()(node a , node b) { return a.w > b.w; } priority_queue <node , vector <node> , cmp> q;
参考文献:
https://www.cnblogs.com/henry-1202/p/9307927.html
https://www.luogu.com.cn/blog/Jydon/wzx-AK-IOI