开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情
堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点 --百度百科
1.插入一个数
2.求集合当中的最小值
3.删除最小值
4.删除任意一个元素
5.修改任意一个元素
STL里面的堆就是优先队列
堆是一棵完全二叉树
小根堆:左右子节点都大于根节点
大根堆:上者相反
存储方式:一维数组,根节点x,左节点2x,右节点2x+1
down(),up() 进行向下向上排序
1.模板介绍
1)初始化
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
//size 是当前节点 (参考前面模拟单链表等)
int h[N], ph[N], hp[N], size;
2)交换
// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
3)获得堆顶
//得到堆顶
int getTop()
{
return h[1];
}
4)排序
void down(int u)
{
int t = u;
//u*2<=size 判断此时节点有左子节点
//h[u*2]<h[t] 正常存储小根堆,那么父节点应该小于子节点
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
//小根堆 左子节点应该小于右子节点
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
//如果此时t 临时节点不为当前节点,说明需要进行排序
if (u != t)
{
//交换后 继续向下搜索排序
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
//u/2父节点存在,并且父节点大于子节点
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
// u/=2 向上移动
u >>= 1;
}
}
5)去掉堆顶
去除堆顶,因为使用一维数组进行存储堆,可以使堆顶为堆尾,然后去除堆尾,进行down操作,使得此时堆顶的堆尾复原
1 2 3 4 5 --> 5 2 3 4 5
5 2 3 4 --> down()进行排序,也可以用up(),只是down知道堆顶是1,up就不知道堆底了
//去除堆顶,因为使用一维数组进行存储堆,可以使堆顶为堆尾,然后去除堆尾,进行down操作,使得此时堆顶的堆尾复原
void Extract()
{
h[1]=h[n--];
down(1);
}
6)初始化堆
// O(n)建堆
//为什么从n/2开始 最大数是n,n/2就是它的父节点,那么每次n/2--,就会遍历整个n
for (int i = n / 2; i; i -- ) down(i);
2.实操
输入一个长度为 nn 的整数数列,从小到大输出前 mm 小的数。
输入格式
第一行包含整数 nn 和 mm。
第二行包含 nn 个整数,表示整数数列。
输出格式
共一行,包含 mm 个整数,表示整数数列中前 mm 小的数。
数据范围
1≤m≤n≤1051≤m≤n≤105,
1≤数列中元素≤1091≤数列中元素≤109输入样例:
5 3 4 5 1 3 2输出样例:
1 2 3
#include<iostream>
using namespace std;
const int N=1e6+10;
int h[N],Size;
void down(int x){...}
void remove(){...}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>h[i];
}
Size=n;
for(int i=n/2;i;i--)
{
down(i);
}
while(m--)
{
cout<<h[1]<<" ";
remove();
}
return 0;
}
维护一个集合,初始时集合为空,支持如下几种操作:
I x,插入一个数 x;PM,输出当前集合中的最小值;DM,删除当前集合中的最小值(数据保证此时的最小值唯一);D k,删除第 k 个插入的数;C k x,修改第 k 个插入的数,将其变为 x;现在要进行 N 次操作,对于所有第 2 个操作,输出当前集合的最小值。
输入格式
第一行包含整数 NN。
接下来 NN 行,每行包含一个操作指令,操作指令为
I x,PM,DM,D k或C k x中的一种。输出格式
对于每个输出指令
PM,输出一个结果,表示当前集合中的最小值。每个结果占一行。
数据范围
1≤N≤1e5 −1e9≤x≤1e9 数据保证合法。
输入样例:
8 I -10 PM I -10 D 1 C 2 8 I 6 PM DM输出样例:
-10 6
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int h[N]; //堆
int ph[N]; //存放第k个插入点的下标
int hp[N]; //存放堆中点的插入次序
int Size;
void swap_1(int u,int v){}
void down(int x){}
void up(int x){}
int main()
{
int n;
//插入的次序
int m=0;
cin>>n;
while(n--)
{
string op;
int x,y;
cin>>op;
//插入
if(op=="I"){
cin>>x;
m++;
//先存储数据 c++ size变为关键字了
h[++Size]=x;
//存储当前数据的插入次序
hp[Size]=m;
//当前插入次序的下标
ph[m]=Size;
// 放入底端进行向上操作
up(Size);
}else if(op=="PM"){
//输出顶端 h[[1]即可
cout<<h[1]<<endl;
}else if(op=="DM"){
//删除顶端 看模板
swap_1(1,Size);
Size--;
down(1);
}else if(op=="D"){
//输入是第x个插入的
cin>>x;
//得到在堆中的下标
int u=ph[x];
//进行交换 类似删除顶端的操作
swap_1(u,Size);
Size--;
//因为不知道交换而来的是大数还是小数,所需要up,down各一次
up(u);
down(u);
}else if(op=="C"){
//第x个插入的 改为y
cin>>x>>y;
//ph[x]得到下标 h[ph[x]]=y 进行修改
h[ph[x]]=y;
//同上"D"操纵,不知道大小数,down up各一次
down(ph[x]);
up(ph[x]);
}
}
return 0;
}