手写堆
堆:
- 堆中某个结点的值总是不大于或不小于其父结点的值;
- 堆总是一棵完全二叉树。
- 完全二叉树:如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。
这样就不难理解优先队列的原理,即取堆顶的元素,并时刻更新堆顶元素
手写堆的存储
手写堆时,直接使用一个一维数组来存储,因为堆是一个完全二叉树。
根节点 heap[1]。
节点 x 的左儿子:2x
节点 x 的右儿子:2x + 1
手写堆的两个基本操作
手写堆的所有功能都是通过这两个基本操作实现的:
down( x ) :将一个节点向上移。
up( x ) :将一个节点向下移。
down 与 up 都是与树的高度成正比的,所以时间复杂度是 O ( l o g n ) O(logn)O(logn) 的。
————————————————
版权声明:本文为CSDN博主「theSunAndSnow」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44960253/article/details/110245920
const int N = 100010;
int n, m;
int heap[N], size;
void down(int u) {
int t = u; // 用 t 存储三个节点(u 节点和它的两个子节点)中的最小值
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; // 右儿子是否存在 && 右儿子是否比父节点小
if (u != t) { // 说明根节点不是三个节点中的最小值
swap(heap[u], heap[t]);
down(t);
}
}
void up(int u) {
while (u / 2 && heap[u / 2] > heap[u]) {
swap(heap[u], heap[u / 2]);
u /= 2;
}
}
插入节点的原理
时间复杂度:O( log n )
我们首先将新的节点放在一维数组的最后一个位置。然后尝试使用 up 操作
heap[++size] = x; // size 表示堆中已经存储了的节点个数
up(size);
求当前堆的最小值
时间复杂度:O( 1 )
heap[1];
删除堆顶(最小值)
时间复杂度:O( log n ) ————原文疑似有误
思路:用堆的最后一个元素来覆盖掉堆顶元素,然后 size–; 然后执行 down 操作。
使用这个思路的原因是:因为我们使用一维数组来模拟堆,所以我们删除头节点会特别困难,但是删除一维数组的最后一个元素代价会很小(size --)。
heap[1] = heap[size];
size--;
down(1);
删除任意一个元素
时间复杂度:O( log n )
思路类似于删除堆顶。
假设删除节点 k 。
heap[k] = heap[size];
size--;
up(k);
down(k); // up 与 down 只会执行一个
手写堆的初始化
我们当然可以用插入操作来建堆,时间复杂度是:O( nlog n )。
但也可以从 n 处开始 down 操作,其实就是 从 二叉树的倒数第二层开始执行 down 操作。
for (int i = 1; i <= n; ++i) scanf("%d", &heap[i]);
for (int i = n; i; --i) down(i); // n 表示 节点个数
时间复杂度也是O(n log n)
板子:手写堆(我的)
#include<stdio.h>
#include<iostream>
using namespace std;
#define N 1000010
typedef int T;
T heap[N];//从1开始
int Size=0;
//小顶堆体现
void up(int k){
if(k/2&&heap[k/2]>heap[k]){
swap(heap[k/2],heap[k]);
up(k/2);
}
}
void down(int k){
int t=k;
if(2*k<=Size&&heap[2*k]<heap[t])t=2*k;
if(2*k+1<=Size&&heap[2*k+1]<heap[t])t=2*k+1;
if(t!=k){
swap(heap[t],heap[k]);
down(t);
}
}
int size(){
return Size;
}
void empty(){
Size=0;
}
void push(T k){
heap[++Size]=k;
up(Size);
}
void pop(){
heap[1]=heap[Size--];
down(1);
}
T top(){
return heap[1];
}
int main(){
int n;
scanf("%d",&n);
int k;
for(int i=1;i<=n;i++){
scanf("%d",&k);
push(k);
}
for(int i=1;i<=Size;i*=2){
for(int j=0;j<i;j++){
printf("%d\t",heap[i+j]);
}
printf("\n");
}
return 0;
}