数据结构:堆手写

手写堆

堆:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;
  • 堆总是一棵完全二叉树。
  • 完全二叉树:如果编号为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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值