堆排序——杨子曰算法
没错,它又是一个n log n的算法,有人说:学这么多排序有什么卵用?
杨子曰:装逼
堆排是用一个堆来实现的(废话)
我们这里说的堆是小根堆——就是爸爸比儿子要小的堆(听上去好别扭)
堆排的实现要分为两个部分:建堆和拆堆
我们用3,4,6,1,5,2来模拟一下
黑喂狗:
1.建堆
我们在建堆是一定要维护爸爸比儿子小,具体怎么实现呢?拿到一个数后不管三七二十一先把它放到堆底,然后和他的父亲进行比较,如果比他的父亲小,就交换,直到到顶了或者比父亲大了就停止(专业人士称之为上浮),我们用3,4,6,1,5,2来模拟一下
先把3扔进去
然后把4放到堆底
发现小根堆很和谐
继续
依然很和谐,再继续
惊悚的发现刚刚放进去的1比他爸爸小了,So我们交换
哦,依然比他爸爸小,再交换
嗯,Great,我们的小根堆依然和谐
然后就可以把5扔进去了
嗯,不用交换,继续
发现2比爸爸6小了,So上浮
OK,这下彻底和谐了,整棵堆也被我们建完了╰(๑◕ ▽ ◕๑)╯
2.拆堆
顾名思义我们要把刚才建的堆拆掉(好心痛)——拆完以后我们的数组就排好了
具体实现:
每次弹出堆顶元素,然后把堆底元素放到堆顶,然后这将这个元素与它较小的儿子不断交换(在维护小根堆),一直到底(专业人士称之为下沉)我们还是用刚才的例子:
首先弹出堆顶元素1
把堆底元素6放到堆顶
然后你就会发现小根堆不和谐了,我们把6下沉,与儿子中较小的2交换
嗯,小根堆维护好了,然后继续弹出2
把堆底元素5放到堆顶
开始下沉,3较小,交换
继续下沉
弹出3
把5移上来
下沉
弹出4
把6移上来
下沉
弹出5
把6移上来
弹出6
惊奇的发现我们已经排完了!
总结一波:其实堆排就是用堆来维护最小值,然后不停把最小值放到最前面——你可以把它理解成堆优化的选择排序,维护堆的复杂度是O(log n),排序时间复杂度O(n log n)
给第一次写堆的童鞋一个小tip:
堆可以用一个数组存下,用a[i]表示从上往下,从左往右数第i个节点,那么节点i的父亲就是i/2,节点i的两个儿子就是 i * 2 和 i * 2+1
OK,完事
c++模板:
#include<bits/stdc++.h>
using namespace std;
const int inf=200000000;
int n,heap[100005],x[100005];
int main(){
int n;
scanf("%d",&n);
for (int i=1;i<=2*n;i++){
heap[i]=inf;
}
int num=0,nod;
for (int i=1;i<=n;i++){
nod=++num;
int k;
scanf("%d",&k);
heap[nod]=k;
while(nod!=1&&heap[nod]<heap[nod/2] ) swap(heap[nod],heap[nod/2]),nod/=2;
}
int index=0;
for (int i=1;i<=n;i++){
x[++index]=heap[1];
heap[1]=heap[num];
heap[num--]=inf;
int nod=1;
while(heap[nod*2]<heap[nod]||heap[nod*2+1]<heap[nod]){
if (heap[nod*2]<heap[nod*2+1]) swap(heap[nod],heap[nod*2]),nod*=2;
else swap(heap[nod],heap[nod*2+1]),nod=nod*2+1;
}
}
for (int i=1;i<=n;i++){
cout<<x[i]<<' ';
}
return 0;
}
于XJ机房607