关于堆排序
Preface:
本来这个蒟蒻并不想水这一篇水水的博客,但在昨天看到某人在听完课之后,自信地发了一篇关于并查集的拓展域的博客(知识点还漏了不少)后脑洞大开,于是在刚学完之后水了一篇关于堆排序的博客。(初学堆,讲不好别打我)
Part 1:堆(heap)
堆排序的重点就是利用堆的性质,在这之前,首先要了解堆。堆在本质上是一棵完全二叉树。一棵完全二叉树中,左儿子=父亲×2,右儿子=父亲×2+1。
堆分为两种,大根堆和小根堆。大根堆就是在这棵完全二叉树中,任意一个父亲节点大于等于它的两个儿子节点。小根堆同理,就是在这棵完全二叉树中,任意一个父亲节点小于等于它的两个儿子节点。如下图所示
蒟蒻画的图,将就着看吧
仔细观察这两张图,不难发现大根堆中的最大数就是它的根节点,小根堆中的最小数就是它的根节点。
Part 2:Solution
根据,大根堆中的最大数就是它的根节点,小根堆中的最小数就是它的根节点这一结论,我们就来分析一下堆排序的基本思路(这里讲从大到小的排序)。
1. 读入n,再读入n个数,每读入一个数,就把它们插入到堆中。只要每次把输入元素扔到最后,再利用堆的性质与它的父亲比较,如果不满足性质就交换,直到满足性质
2.这里因为从大到小,所以用小根堆。每次把堆顶元素(一定是最小的)扔到最后,实际上是把堆顶和最后一个元素交换,再次利用堆的性质,把堆顶元素不停和他的儿子比较,如果不满足性质就交换,直到满足性质。
3.每次取出的堆顶元素一定是该堆中最小的,并且该堆中的最大元素在最后一定是堆顶,所以只要把堆中的每个数按顺序依次输出即可
Part 3:Code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000;
int n,i,t,x,a[maxn];
void up(int x);
void down();
int main(){
cin>>n;
for(i=1;i<=n;i++)
{
cin>>x;
up(x); //每读入一个数,就插入到堆中
}
for(i=1;i<=n;i++)
{
swap(a[1],a[t]);//交换堆顶和最后一个元素
t--;//堆中剩下元素个数-1
down();
}
for(i=1;i<=n;i++) cout<<a[i]<<" ";//从大到小依次输出
cout<<"\n";
return 0;//养成好习惯
}
void up(int x){
int i;
t++;
a[t]=x;//把新元素默认成最后一个
i=t;
while(i>1&&a[i]<a[i/2]) //当不满足时
{
swap(a[i],a[i/2]);//交换父亲,儿子
i/=2;
}
}
void down(){
int i=1;
int son;
while(i*2<=t)//当左儿子还在
{
son=i*2;//默认左儿子小
if(son+1<=t&&a[son+1]<a[son]) son++;
//贪心,如果右儿子还在且它比左儿子小
if(a[i]>a[son]) swap(a[i],a[son]),i=son;
//父亲一定要比儿子大才能交换
else break;
//已经满足要求
}
}