堆排序
由一颗完全二叉树组成。
小根堆:每个节点的值都小于等于它的左右儿子节点的值,根节点是最小的值
大根堆:每个节点的值都 大于等于它的左右儿子节点的值,根节点是最大的值
堆的功能(以手写的小根堆为例)
1、插入一个数
2、求集合中的最小值
3、删除最小值
4、删除任意一个元素
5、修改任意一个元素
P S PS PS:用 s i z siz siz 来维护当前堆的大小,每进行一次删除操作都要 s i z − 1 siz-1 siz−1。另外,数组是以 1 1 1 开始的,所以 k k k 节点的左子树是 2 ∗ k 2*k 2∗k ,右子树是 2 ∗ k + 1 2*k+1 2∗k+1 。以 0 0 0 开始的话左子树就是 2 ∗ k + 1 2*k+1 2∗k+1 ,右子树是 2 ∗ k + 2 2*k+2 2∗k+2
down()操作
t是求左节点和右节点的之中最小值的下标,然后再向下递归
void down(int u)
{
int t = u;
if(u * 2 <= siz && h[u * 2] < h[t])
t = u * 2;
if(u * 2 + 1 <= siz && h[u * 2 + 1] < h[t])
t = u * 2 + 1;
if(t != u)
{
swap(h[t], h[u]);
down(t);
}
}
up()操作
不管是左子树还是右子树,他的下标除以2一定是他们的父亲节点
void up(int u)
{
while(u / 2 && h[u] < h[u / 2])
{
swap(h[u], h[u / 2]);
u/=2;
}
}
删除最小值
先把根节点和最后一个点的值交换,再进行down()操作,注意要--siz
{
arr[1]=arr[siz];
--siz;
down(1);
}
模板题链接:AcWing 839.模拟堆
A
C
AC
AC代码里有两个数组,分别是
h
p
hp
hp和
p
h
ph
ph
p
h
ph
ph表示第
k
k
k个插入的数在堆中什么位置,里面的参数是这个数是第几个被插入的
h
p
hp
hp表示在第
n
n
n个位置上的数是第几个插入的,里面的参数是这个数是在堆中什么位置
本题的难点就在于如和快速找到第k个插入的数在堆里面的位置,也就是在堆里面的下标
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);//此时ph里面的都是这个数是第几个插入的,返回的是这个数在堆中的位置
swap(hp[a],hp[b]);//此时hp里面的是这个数在堆中的位置,返回的是这个数是第几个被插入的
swap(h[a], h[b]);
}
顺利交换两个数,不仅要交换两个数在堆中的数值,还要在
h
p
hp
hp数组里面交换这两个数在堆中的位置,以及
p
h
ph
ph 数组里面是第几个被插入的
这里
h
e
a
p
_
s
w
a
p
heap\_swap
heap_swap 里面的
a
,
b
a,b
a,b 参数都是表示这个数在堆中的位置,比如
t
t
t 和
u
u
u 都是表示在堆中的位置。删除第
k
k
k 个数的时候,因为
k
k
k 已经被处理成了
k
=
p
h
[
k
]
k=ph[k]
k=ph[k] 。而
s
i
z
siz
siz 本来就表示它在队中最后一个位置,不用管。
这里以交换第
k
k
k 个数和最后一个
s
i
z
siz
siz 数为例:
首先
k
k
k 是表示第几个被插入,
p
h
[
k
]
ph[k]
ph[k] 表示第
k
k
k 个插入的数在堆中什么位置,
h
p
[
a
]
hp[a]
hp[a] 和
h
p
[
b
]
hp[b]
hp[b] 分别表示
a
,
b
a,b
a,b 这两个位置的数是第几个被插入的,然后用
p
h
[
h
p
[
a
]
]
ph[hp[a]]
ph[hp[a]] ,和
p
h
[
h
p
[
b
]
]
ph[hp[b]]
ph[hp[b]] 交换这个第
k
k
k 插入的数和第
s
i
z
siz
siz 个插入的数的在堆中的位置,最后
s
i
z
siz
siz 要
−
1
-1
−1 的。然后用
h
p
[
a
]
hp[a]
hp[a] 和
h
p
[
b
]
hp[b]
hp[b] 交换
a
,
b
a,b
a,b 两个位置的数是第几个被插入的这个第几个,最后交换两个值。始终记得我们的核心是要找到第k个插入的数在堆中的什么位置,而找到这个位置需要ph和hp两个数组共同维护
,比如
k
=
p
h
[
k
]
k=ph[k]
k=ph[k] 的时候我们需要用到
p
h
ph
ph 数组,而在
h
e
a
p
_
s
w
a
p
heap\_swap
heap_swap数组里面的
p
h
ph
ph 数组又需要
h
p
hp
hp 数组维护
A
C
AC
AC 代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
int h[MAXN], ph[MAXN],hp[MAXN];//ph表示第k个插入的数在堆中什么位置
//hp表示在第n个位置上的数是第几个插入的
int N, siz, k, x;
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);//此时ph里面的都是这个数是第几个插入的,返回的是这个数在堆中的位置
swap(hp[a],hp[b]);//此时hp里面的是这个数在堆中的位置,返回的是这个数是第几个被插入的
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if(u * 2 <= siz && h[u * 2] < h[t])
t = u * 2;
if(u * 2 + 1 <= siz && h[u * 2 + 1] < h[t])
t = u * 2 + 1;
if(t != u)
{
heap_swap(t, u);
down(t);
}
}
void up(int u)
{
while(u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u/=2;
}
}
int main()
{
int m = 0;
scanf("%d", &N);
while(N--)
{
string str;
cin >> str;
if(str == "I")
{
scanf("%d", &x);
++siz;
++m;
h[siz] = x;
ph[m] = siz;
hp[siz]=m;
up(siz);
}
else if(str == "PM")
printf("%d\n", h[1]);
else if(str == "DM")
{
heap_swap(1, siz);
--siz;
down(1);
}
else if(str == "D")
{
scanf("%d", &k);
k = ph[k];
heap_swap(k, siz);
--siz;
down(k);
up(k);
}
else
{
scanf("%d%d", &k, &x);
k = ph[k];
h[k] = x;
down(k);
up(k);
}
}
return 0;
}