堆的实现

数据结构------堆(二、堆的实现) 一、堆的定义   我们知道堆是一个树形结构,其实堆的底层是一棵完全二叉树。而完全二叉树是一层一层按照进入的顺序排成的。按照这个特性,我们可以用数组来按照完全二叉树实现堆。    上面的图片就是一个完全二叉树,也是一个最大堆。而最大堆有一个性质:每一个节点的值都小于它父节点的值。我们也可以从上面的图片中看出来。但是需要注意的是,每一个节点的值的大小与它所处的深度没有必然的联系。因为我们可以看到第三层... 阅读详情

目录

摘要:

一:堆的概念

二:堆实现的难点理解

三:堆的实现框架

四:函数实现

1:初始化堆

2:交换函数

3:插入函数

4:向上调整函数

5:删除函数

6:向下调整函数

7:取堆顶

8:判空

9:获取堆的元素个数

10:打印

11:销毁堆

12:建堆函数

五:代码

1:Heap.h

2:Heap.c

六:测试

七:求大堆


摘要:

本博客从0实现堆,从堆的概念到实现的难点,再到每个函数内部实现的解析,然后进行代码测试,最后总结求大堆和求小堆,在向上/向下调整函数中的区别


 

一:堆的概念

堆是一种特殊的完全二叉树结构,其中每个节点的值都必须满足特定的堆属性:对于大堆,父节点的值大于或等于其子节点;对于小堆,父节点的值小于或等于其子节点。这意味着大堆的根节点是最大值,小堆的根节点是最小值。本文将以小堆为例进行详细讲解。

注:大堆也叫作大根堆,小堆也叫作小根堆,这样叫侧面说明了堆顶是最值的特性


 

二:堆实现的难点理解

1:数组 OR 链式结构?

二叉树可以用链式结构实现,也可以用数组实现,对于普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用数组实现,现实中我们通常把堆(完全二叉树)使用顺序结构的数组来实现

对于二叉树来说最重要的就是能够找到自己的左右孩子,所以如果采取链式结构实现,则单个节点内部具有两个指针即可找到两个孩子,如果采取数组实现,则利用下标的特性来存储二叉树,也可以找到对应的左右孩子,甚至还可以通过孩子的下标找到父的下标,而数组对于普通二叉树会浪费一些空间,这是唯一的缺点,但对于完全二叉树,则只剩优点了

注:有时我也会叫做插入的值为节点,只是为了方便讲述和理解,本质实现结构还是数组....

如图:


 

2:用数组来逐层存储堆,可以得到三个公式

leftchild  = parent *2 +1(左孩子的下标等于父亲的下标*2+1)
例:D的下标 = B的下标1 去*2 + 1 = 3

rightchild = parent *2 +2(右孩子的下标等于父亲的下标*2+2)
例:E的下标 = B的下标1 去*2 + 2 = 4

parent = (child-1)/2*(父亲的下标等于孩子的下标-1再/2)
例:B的下标 = D的下标(3-1)/2 = 1 或 E的下标(4-1)/2 =1(int的运算法则,1.5也是1)

注:这就是前文所说的用数组存储二叉树可以找到孩子的原因,下标之间具有规律


 

3:堆的插入

堆的实现难点之一,就是堆的插入,如果是普通的二叉树,插入也就是直接根据下标放入值,但是堆则不同,堆具有特性,在插入之后,必须保证堆仍为堆,所以我们在插入之后,需要用到向上调整函数(AdjustUp)

注:为什么叫向上调整,因为堆插入值,就是在最后一个元素的后面进行插入,此时按照二叉树的结构来看,该插入值是位于最底层的,而要通过AdjustUp函数使得插入后形成新的堆,则需要将插入的值往上进行调整,停留在正确的位置...

画图解释,向上调整函数的内部逻辑,都以小堆为例.....

插入的几种情况:

第一种:插入的孩子值直接大于父亲(90>56)

解释:插入进行仍为小堆,所以不作任何处理

第二种:插入的孩子值小于父亲,但未小于根节点(50<56 但 50>10)

解释:插入的值小于父亲,则直接交换,再进行下一轮比较,发现不小于下一个父亲,则停留

第三种:插入的孩子节点直接小于了根节点(5<10)

解释:一直比较父亲,最后发现比堆顶还小,则交换后,停留

总结:向上调整函数(AdjustUp)的本质:当孩子小于父亲,即交换,以此类推。时间复杂度为logn,极其高效...


 

4:堆的删除

堆的难点之二就是堆的删除,对于堆而言,若删除最后一个叶子节点,则毫无意义,直接根据下标找到值删除即可,所以对于堆而言,堆的删除指的是删除堆顶!所以我们要确保每次删除后,他都还是一个堆!

那我们删除堆顶之后,剩余的数组整体向左移动之后,再进行恢复成堆吗?这种想法必错!

①:时间复杂度太高,为O(n)

②:整体向左移动,原有的下标的父子映射关系全部失效

③:最重要的一点是,整体左移之后,我们无法保证新的完全二叉树仍为堆!

正确做法:既然删除堆顶,则我们将堆顶和最后一个元素交换,然后元素个数--,达到删除的效果,此时剩余的完全二叉树,除开堆顶元素,其余的左子树和右子树仍为堆结构,此时我们对堆顶元素进行向下调整,让堆顶元素位于正确的位置,则达到恢复成堆的效果

重点:

向上调整(Sift-Up)和向下调整(Sift-Down)算法本身都是局部修复算法。它们的设计目标不是从零开始构建一个完整的堆,而是基于已有部分堆结构的假设,去修复单个节点的位置,使其最终融入整个堆。两个函数操作之所以快,是因为它们利用了堆的性质(即只需要与父节点或子节点比较和交换,沿着树的高度移动),能够以O(log n) 的时间完成插入和删除

比如向上调整函数,除了插入的值,其余的值形成的结构仍为堆;

比如向下调整函数,在交换首尾删除尾之后,除了堆顶,其余的值形成的左子树和右子树仍为堆;

Q:那我非要整体左移得到新的完全二叉树,然后再恢复成堆可以吗?

A:正确做法的向下调整和堆的插入的向上调整的时间复杂度都为logn,是非常优秀的,而如果堆的删除,你选择体左移得到新的完全二叉树,然后再恢复成堆的思路的话,那么相当于需要从0开始重新建堆了,你得对剩余的数组的元素从头到尾进行向上调整函数建堆了,时间复杂度肯定是远远高于正确做法

画图解释,向下调整函数的内部逻辑,都以小堆为例.....

所以 向下调整函数(AdjustDown)的本质:

①:先交换根节点10和尾节点90,然后删除尾节点10(即起到删除根节点的作用)

②:然后新的根节点作为父亲(90作为父亲),与左右孩子中的最小一个比较(90与15比较),如果大于最小的孩子(90>15),即交换,以此类推.......

③:最后新的堆的元素个数会比删除前少一个,因为最后一个空间被抹去 

5:向上/向下调整函数的总结

①:向上调整(Sift-Up)和向下调整(Sift-Down)算法本身都是局部修复算法。它们的设计目标不是从零开始构建一个完整的堆,而是基于已有部分堆结构的假设,去修复单个节点的位置,使其最终融入整个堆。两个函数操作之所以快,是因为它们利用了堆的性质(即只需要与父节点或子节点比较和交换,沿着树的高度移动),能够以O(log n) 的时间完成插入和删除!比如向上调整函数,除了插入的值,其余的值形成的结构仍为堆;比如向下调整函数,在交换首尾删除尾之后,除了堆顶,其余的值形成的左子树和右子树仍为堆;

②:向上调整只需与父亲进行比较,而向下调整则需要和孩子中的最值进行比较,小堆则找两个孩子中的min,大堆则找孩子中的max进行比较,而父亲或孩子的下标,则根据公式即可求得

③:向上/向下调整函数的复杂度都为logn

而下面实现的重点就是向上/向下调整函数的实现.....


 

三:堆的实现框架

1:文件分配

本文采用三个文件进行实现
1:Heap.h(堆有关函数的声明)
2:Heap.c(堆有关函数的实现)
3:text.c(对函数的检测)

2:assert

所有的函数都会接受到结构体指针的地址,本文统一命名为php,hp是堆heap的缩写,前面加上p前缀,所以php代表是堆的指针,所以会对php进行assert,确保指针不为空,删除函数还会去assert确保堆中存在元素能够删除

注:后续的断言就不再单独解释....


 

四:函数实现

前提:堆的结构体的声明

解释:

①:我们用数组来实现堆,所以要有一个 数组a

②:size代表堆中元素的个数,capacity代表堆的容量;所以size就是待插入位置的下标

③:以下的函数需要改变堆这个结构体中的时候,都需要向函数传结构体指针(php),才能真正的修改到堆


 

1:初始化堆

解释:初始化结构体中的各项


 

2:交换函数

解释:在向上调整和向下调整中都会用到此函数,用于交换父亲和孩子的值,参数为指针,进行址传递,达到真正的交换彼此


 

3:插入函数

解释:

①:插入先判断是否堆已经为满

②:开辟为什么用realloc

 不管是哪种情况,都会去开辟空间给a,所以我们采用realloc。此处两个重点:

Q1:为什么第一次开辟不用malloc?

A1:我们后面会在a的空间基础上进行再次开辟,所以realloc才对,其次我们初始化的时候a = NULL了,在realloc函数中,接收的指针为NULL,realloc就等同于malloc了,也就相当于第一次开辟用的是malloc。

Q2:为什么不直接用a来接收开辟的空间,而是用了一个tmp中间变量,最后在赋值给a;为什么不用capacity直接接收三目操作符的返回值,而是中间变量newcaoacity?

A2:如果用a接收,如果realloc开辟失败,但是a如果之前不是空栈,那之前的栈元素也会丢失,而用一个tmp去接受,再去判断是否开辟成功会更好保护a之前的数据。capacity同理。

③:此时,确保有容量了,再进行赋值和size的++,最后堆插入的元素进行向上调整,才能成为一个堆。


 

4:向上调整函数

解释:当孩子小于父亲,即交换,以此类推.......

①:因为插入的时候调用向上调整函数,所以参数为插入节点的下标,此节点当然就为孩子节点,所以形参命名为child,然后通过child得到其父亲parent的下标,从而进行循环的比较,比较完一次,则更新child和parent的下标即可比较

②:终止循环条件1:child<=0,确切的说是当child为0的时候,这代表插入的节点经过向上调整之后,已经来到了堆顶的位置,则此时就是正确的位置,所以终止循环while(child>0)

③:终止循环条件2:不满足堆的特性,比如我们实现的小堆,当向上调整的过程中,发现child不小于parent,则代表此时child已经来到了正确的位置,不必再继续向上调整了,所以break终止

Q1:为什么while条件不是parent>=0,对于child最后会成为堆顶的情况来说,当最后一次,child为0的时候,parent的下标不就更新为负数了吗,所以parent>=0也可以作为循环条件?

A2:把parent的下标赋给child之后,parent自己更新为 (0-1)/2,还是0,所以parent的下标最小也是0,若while条件为parent>=0,则死循环!

Q2:既然parent的下标最小恒为0,那child不也是被parent赋值的吗,child最后不也是恒为0吗,为什么while(child>0)可以?

A2:因为这是while(child>0),最后child为0,当然终止循环


 

5:删除函数

解释:

①:断言size是因为删除的前提是确保有元素

②: 根据前文的向下调整的思路,先交换根节点和尾节点,在抹去尾节点,然后向下调整


 

6:向下调整函数

解释:当父亲小于两个孩子的min值,即交换,以此类推

①:因为删除的时候调用向下调整函数,所以参数为堆顶节点的下标,此节点当然就为父亲节点,所以形参命名为parent,然后通过parent得到其左孩子child的下标,再通过child得到右孩子下标,从而得到两个孩子的min值,再进行比较,比较完一次,则更新parent和child的下标即可比较

②:终止循环条件1:child>=n,也就是child越界了。当进行到某一次向下调整时,此时parent已经停留在了正确的位置,但child依旧会通过p*2+1更新坐标,此时发现child求到的下标值越界了,系统才知道parent已经停留在正确位置了,所以终止循环,所以循环条件为while(child<n)

③:终止循环条件1:不满足堆的特性,比如我们实现的小堆,当向下调整的过程中,发现parent不小于child,则代表此时parent已经来到了正确的位置,不必再继续向下调整了,所以break终止

这②③两点和向上调整函数是类似的,但是对于向下调整函数来说,还需考虑找不到右孩子的情况

④:在确定孩子中较小值的时候,我们还需要其根据child+1得到右孩子的下标,对比二者得到min值,但如下图,是可能出现该堆结构,导致p的右孩子不存在的情况,所以为了避免child+1越界,在对比来个孩子时,应先确保child+1<n成立,才会执行&&右面的比较

❌️:不能想要综合两个条件,所以直接让while的条件直接 child<n-1,因为child的下标是可以为n-1的,此时的child就是最后一个元素下标,此时代表还能进行一次向下调整中的比较,万一parent对比这个child之后,需要交换呢?所以若条件为child<n-1,则可能无法让parent调整到正确的位置


 

7:取堆顶

解释:返回数组的第一个元素


 

8:判空

解释:size为0,即为空。


 

9:获取堆的元素个数

解释:size即代表堆的数据个数


 

10:打印

解释:循环打印每一个元素即可,%d后跟一个空格更容易观察。


 

11:销毁堆

解释:将结构体中的一切恢复初始即可。 


 

12:建堆函数

上面的函数已经足够使用了,但是需要一个一个的插入进堆,我们可是再实现一个HeapInitArray建堆函数,直接把一个数组作为参数调用此建堆函数,其实本质就是把每次单个循环插入集合成了一个函数

解释:

①:参数除了php外,还要接受一个数组和数组的元素个数n,n用于malloc堆的数组的大小

②:然后将a数组的内容拷贝到堆结构体的成员变量数组中(php->a)

③:最后对php->a进行建堆,从第二个元素开始向上调整即可,此方法虽然方便理解,但是效率较低,最优方式是从最后一个非叶子节点向下调整建堆,至于为什么,在堆排序博客中有讲

堆排序博客


 

五:代码

1:Heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<time.h>

// 堆的数据类型定义
typedef int HPDataType;

// 堆结构体
typedef struct Heap
{
	HPDataType* a;      // 指向堆数组的指针
	int size;           // 堆中元素的个数
	int capacity;       // 堆的容量
}HP;

// 向上调整堆
void AdjustUp(HPDataType* a, int child);

// 向下调整堆
void AdjustDown(HPDataType* a, int n, int parent);

// 交换
void Swap(HPDataType* p1, HPDataType* p2);

// 打印
void HeapPrint(HP* php);

// 初始化堆
void HeapInit(HP* php);

// 根据给定数组初始化堆
void HeapInitArray(HP* php, int* a, int n);

// 销毁堆,释放内存
void HeapDestroy(HP* php);

// 堆的插入
void HeapPush(HP* php, HPDataType x);

// 堆的删除
void HeapPop(HP* php);

// 获取堆顶元素
HPDataType HeapTop(HP* php);

// 判空
bool HeapEmpty(HP* php);


 

2:Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

/**
 * @brief 初始化堆结构
 * 将堆的指针置空,大小和容量置为0
 */
void HeapInit(HP* php)
{
	assert(php); // 确保传入指针非空

	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

/**
 * @brief 使用给定数组初始化堆
 * 将输入数组拷贝到堆内部数组,然后通过逐个向上调整的方式将其构建成堆
 * 时间复杂度: O(N)
 */
void HeapInitArray(HP* php, int* a, int n)
{
	assert(php);
	assert(a);

	// 为堆分配内存空间
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail"); // 分配失败,打印错误信息
		exit(-1);              // 异常退出程序
	}
	php->size = n;
	php->capacity = n;

	// 拷贝数组内容
	memcpy(php->a, a, sizeof(HPDataType) * n);

	// 从第二个元素开始,逐个向上调整,建立堆结构
	// 此方法比从最后一个非叶子节点开始向下调整更直观,但通常效率略低
	for (int i = 1; i < n; i++)
	{
		AdjustUp(php->a, i);
	}
}

/**
 * @brief 销毁堆,释放其占用的内存
 */
void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a); // 释放动态分配的数组
	php->a = NULL;
	php->size = php->capacity = 0; // 重置大小和容量
}

/**
 * @brief 交换两个HPDataType类型的变量
 */
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

/**
 * @brief 向上调整堆(小根堆)
 * 当一个节点的值变小时,可能需要向上移动以维持堆的性质
 * @param a 指向堆数组的指针
 * @param child 待调整节点的索引
 */
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2; // 计算父节点索引
	while (child > 0) // 如果当前节点不是根节点
	{
		// 如果子节点小于父节点,则违反了小根堆性质,需要交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;          // 更新child为原parent的位置
			parent = (parent - 1) / 2; // 计算新的parent位置
		}
		else
		{
			// 如果已满足堆性质,调整结束
			break;
		}
	}
}


/**
 * @brief 向下调整堆(小根堆)
 * 当一个节点的值变大时,可能需要向下移动以维持堆的性质
 * @param a 指向堆数组的指针
 * @param n 堆的有效元素个数,用于边界检查
 * @param parent 待调整节点的索引
 */
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1; // 计算左孩子的索引
	while (child < n) // 如果左孩子存在
	{
		// 在左右孩子中找出较小的那个
		if (child + 1 < n && a[child + 1] < a[child]) // 检查右孩子是否存在且更小
		{
			++child; // 选择右孩子
		}

		// 如果最小的孩子比父节点还小,则违反了小根堆性质,需要交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			// 继续向下调整,更新parent和child为新的位置
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			// 如果已满足堆性质,调整结束
			break;
		}
	}
}

/**
 * @brief 向堆中添加一个元素
 * 插入到数组末尾,然后进行向上调整以维持堆性质
 */
void HeapPush(HP* php, HPDataType x)
{
	assert(php);

	// 检查容量,如果不足则扩容
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2; // 初始容量为4,后续翻倍
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail"); // 扩容失败,打印错误信息
			exit(-1);               // 异常退出程序
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

	// 将新元素放入数组末尾
	php->a[php->size] = x;
	php->size++;

	// 对新插入的元素进行向上调整
	AdjustUp(php->a, php->size - 1);
}

/**
 * @brief 打印堆中所有元素
 */
void HeapPrint(HP* php)
{
	assert(php);

	for (size_t i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]); // 依次打印元素,以空格分隔
	}
	printf("\n"); // 换行
}

/**
 * @brief 删除堆顶元素(对于小根堆,即最小元素)
 * 将堆顶与最后一个元素交换,然后对新的堆顶进行向下调整
 */
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0); // 确保堆非空

	// 将堆顶元素与最后一个元素交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	--php->size; // 减少堆的大小

	// 对新的堆顶元素进行向下调整,恢复堆性质
	AdjustDown(php->a, php->size, 0);
}

/**
 * @brief 获取堆顶元素的值
 * @return 返回堆顶元素的值(对于小根堆,即最小值)
 */
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0); // 确保堆非空

	return php->a[0]; // 返回数组第一个元素
}

/**
 * @brief 检查堆是否为空
 * @return 如果堆为空则返回true,否则返回false
 */
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0; // 直接判断size是否为0
}


 

六:测试

test.c

#pragma once
#include "Heap.h"
#include <stdio.h>
#include <stdlib.h> // 需要包含 malloc 和 free

void TestBasicOperations()
{
	printf("=== 测试基本操作 ===\n");
	HP hp;
	HeapInit(&hp);

	// 测试 Push 和 Print
	printf("1. 插入元素 [50, 30, 70, 20, 40, 60, 80]:\n");
	int values[] = { 50, 30, 70, 20, 40, 60, 80 };
	for (int i = 0; i < 7; ++i)
	{
		HeapPush(&hp, values[i]);
	}
	printf("   堆内容 (应为小根堆): ");
	HeapPrint(&hp);

	// 测试 Top
	printf("2. 堆顶元素 (应为最小值 20): %d\n", HeapTop(&hp));

	// 测试 Pop
	printf("3. 弹出堆顶元素 (20):\n");
	HeapPop(&hp);
	printf("   堆内容 (应为新的小根堆): ");
	HeapPrint(&hp);
	printf("   新的堆顶元素 (应为 30): %d\n", HeapTop(&hp));

	// 测试 Empty
	printf("4. 堆是否为空 (应为 false): %s\n", HeapEmpty(&hp) ? "true" : "false");

	// 清空堆
	while (!HeapEmpty(&hp))
	{
		printf("   弹出: %d, ", HeapTop(&hp));
		HeapPop(&hp);
		printf("剩余: %s\n", HeapEmpty(&hp) ? "empty" : "not empty");
	}

	HeapDestroy(&hp);
	printf("--- 基本操作测试完成 ---\n\n");
}

void TestInitArray()
{
	printf("=== 测试 InitArray (建堆) ===\n");
	HP hp;
	int arr[] = { 9, 5, 2, 7, 3, 8, 1, 6, 4 };
	int n = sizeof(arr) / sizeof(arr[0]);

	printf("1. 原始数组: ");
	for (int i = 0; i < n; ++i) printf("%d ", arr[i]); printf("\n");

	HeapInitArray(&hp, arr, n);
	printf("2. 用数组初始化堆后 (应为小根堆): ");
	HeapPrint(&hp);
	printf("   堆顶元素 (应为最小值 1): %d\n", HeapTop(&hp));

	HeapDestroy(&hp);
	printf("--- InitArray 测试完成 ---\n\n");
}

void TestEdgeCases()
{
	printf("=== 测试边界情况 ===\n");
	HP hp;
	HeapInit(&hp);

	// 测试空堆
	printf("1. 测试空堆:\n");
	printf("   是否为空 (应为 true): %s\n", HeapEmpty(&hp) ? "true" : "false");
	// 注意:调用 HeapTop 或 HeapPop 在空堆上会触发 assert,这里不演示

	// 测试单个元素
	printf("2. 插入单个元素 42:\n");
	HeapPush(&hp, 42);
	printf("   堆内容: ");
	HeapPrint(&hp);
	printf("   堆顶 (应为 42): %d\n", HeapTop(&hp));
	printf("   是否为空 (应为 false): %s\n", HeapEmpty(&hp) ? "true" : "false");

	printf("   弹出唯一元素:\n");
	HeapPop(&hp);
	printf("   是否为空 (应为 true): %s\n", HeapEmpty(&hp) ? "true" : "false");

	HeapDestroy(&hp);
	printf("--- 边界情况测试完成 ---\n\n");
}


int main()
{
	TestBasicOperations();// 测试基本操作
	TestInitArray();// 测试 InitArray (建堆)
	TestEdgeCases();// 测试边界情况

	printf("所有测试用例执行完毕。\n");

	return 0;
}


 

七:求大堆

如果要实现大堆该怎么做?只需要改变几个符号即可

当然,死记硬背是没用的,重点在于理解:

向上调整函数,子大于父进行交换,代表大的值往上层走,所以目标是大堆,反之小堆;

向下调整函数,父大于子进行交换,代表大的值往下层走,所以目标是小堆,反之大堆;

📌 [ 作者 ]   shylyly
📃 [ 首次发布 ]   2024.8.10
❌ [ 最新修改 ]   2026.5.30
📜 [ 声明 ]   由于笔者水平有限,文中难免有疏漏或不妥之处,还望读者不吝赐教

堆的原理实现详解以及堆的应用 文章目录堆是什么堆的存储方式插入结点删除结点堆排序代码实现 堆是什么   堆(heap)本质上就是一棵完全二叉树,只不过这棵完全二叉树有点特别,能够被称为堆的二叉树,除了它必须要是一棵完全二叉树外,它的每一个结点都必须大于或者小于其子节点。 解释一下就是对于树中的所有的结点都至少有一棵子树,对于这个结点的子树上所有的结点都必须要大于或者小于其父节点。因此,堆也是个递归的定义,通常,我们将结点都大于或者等于子树所有结点的堆称为最大堆 ;将结点都小于或者等于子树所有结点的堆称为最小堆 。如下图所示: 阅读详情

相关推荐

Kettle安装与使用

Pentaho Data Integration作为一个端对端的数据集成平台,可以对多种数据源进行抽取(Extraction)、加载(Loading)、数据落湖(Data Lake Injection)、对数据进行各种清洗(Cleasing)、转换(Transformation)、混合(Blending),并支持多维联机分析处理(OLAP)和数据挖掘(Data mining)。勺子(spoon.bat/spoon.sh):是一个图形化的界面,可以让我们用图形化的方式开发转换和作业。

jnrjian的博客 1万+

【数据结构】堆的实现(简单易懂,超级详细!!!)

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。本文从堆的概念和规律,到堆的实现,向下调整算法,建堆,向上调整算法等等,详细讲解,图文并茂!

m0_65679465的博客 7304

react +ant desiagn pro实现按钮操作权限控制

在ant desiagn pro中提供了一个access用于对权限进行控制,需引入useAccess和Access

Sylvasylva的博客 2208

数据结构-堆的实现(详细图解+解说)

本文详细介绍了数据结构-堆的实现

Eliauk_U的博客 3362

VQGAN实战:从文本到高清图像的保姆级生成教程(含PyTorch代码)

本文详细介绍了VQGAN(Vector Quantized Generative Adversarial Network)在文本到高清图像生成中的应用,提供了从环境配置到模型加载、文本编码及图像生成的完整PyTorch代码教程。通过实战案例和高级调优技巧,帮助开发者快速掌握VQGAN的核心技术,实现百万像素级的高清图像生成。

u8v9w0x1y的博客 644

多个MOS管并联应用场景的四大要点

动态运行时,阈值电压Vgsth最低的MOS管首先打开,最后关闭。这种MOS管一般会占据更多的开关损耗,并且在开关转换过程中,承担了更高的电应力。

VBsemi的博客 3735

【数据结构】超详细——堆的实现

堆是二叉树的一种应用展现,与序列、链表等相比更有工程应用价值。

joes_ju的博客 2076

【全解析 | PTA】浙大版《Python 程序设计》题目集-第一章

Python浙大版PTA题解

wzhh_的博客 5516

堆的实现详解

堆的实现以及top-k问题

weixin_70238476的博客 2714

Java全栈开发:充电桩管理系统项目实战

技术收获深入理解分布式系统架构掌握高并发场景解决方案提升系统设计能力经验教训前期需求分析的重要性技术选型需要考虑长期维护测试覆盖率的重要性未来展望引入AI预测故障支持更多支付方式优化用户体验通过此项目的开发,不仅实现了充电桩的智能化管理,也积累了宝贵的全栈开发经验。项目中的很多技术方案和架构设计都具有普遍适用性,可以在其他类似项目中借鉴使用。@Componentstatic {// 初始化状态转换规则。

exlink2012的专栏 1246

第七章:GPU Scheduler 分析:7.3 drm_sched_entity — 调度实体

本文介绍了DRM GPU调度器中的drm_sched_entity结构,它作为用户提交命令进入调度器的提交通道,解决了归属、排序和公平性问题。文章首先分析了命令提交的归属需求,然后详细阐述了entity的核心模型,包括其职责、进程映射关系和核心字段。接着说明了用户态提交进入内核后的关键节点路径,以及entity内部的FIFO语义和激活机制。entity通过组织同一context的命令提交包为FIFO队列,使调度器能够在多个entity之间进行优先级和公平性调度,同时保证同一提交通道的顺序执行。

图形/计算GPU的驱动与虚拟化技术分享园地 604

堆的实现以及利用堆进行排序

堆是一种特殊的完全二叉树,分为最大堆和最小堆。在最小堆中,父节点的值总是小于或等于其子节点的值。这种特性使得堆的根节点始终是所有节点中的最小值,非常适合实现优先队列。堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;堆总是⼀棵完全⼆叉树。⼆叉树性质对于具有 n 个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的数组顺序对所有结点从0 开始编号,则对于序号为 i 的结点有:若 i>0 , i 位置结点的双亲序号: (i-1)/2;i=0 , i 为根结点编号,⽆双亲结点。

zzh_Zao的博客 1830

CDD数据库文件制作(二)——服务配置(0x27)

主要根据诊断调查表,针对0x27服务在CDD中的每一步配置给出详细介绍 1.会话切换配置 1.1.根据诊断调查表进行信息提取 1.2.子功能添加 1.2.1.Level1安全等级1创建 1.2.2.Level3安全等级3创建 1.2.3.安全等级名字查看与修改 1.3.会话切换信息提取(应用程序下) 1.4.会话转换配置(应用程序下) 1.5.会话切换信息提取(引导程序下) 1.6.会话转换配置(引导程序下) 2.安全等级配置信息提取 2.1.安全访问配置(level1) 2.2.安全访问配置(level2

LOVE135149的博客 2922

【数据结构】堆的实现以及建堆算法和堆排序

哈喽,各位小伙伴大家好!上期给大家讲了树,二叉树以及堆。今天带着大家实现堆这个数据结构,以及堆排序。话不多说,咱们进入正题!向大厂冲锋!

大白的编程日记. 2011

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

堆——C语言实现

堆是一种数据结构,他的特点在于形成某种优先的结构。在计算机经常用到,比如优先队列,或者是优先进程管理。 堆(也叫二叉堆)的性质: 1、任何一个节点,都不大于他的父亲节点。 2、必须是一颗完全二叉树 比如下面: 我们用一个很经典的堆的实现方法:用数组实现堆。 注意,这里实现的叫最大堆,就是把最大元素放在顶端,最小堆的实现方法相同。 如图,我们给一个堆的每个元素编号:   这...

likunkun__的博客 3782

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

堆的实现(堆的插入、堆的删除等)超级全

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

萌新xiaoxu的博客 1414

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

堆的数组实现

用数组实现堆

2302_76314368的博客 1523

【堆】堆的实现(Java)

  今天学习堆这种数据结构。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质: 堆中某个结点的值总是不大于或不小于其父结点的值; 堆总是一棵完全二叉树。 将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。 1.用优先队列实现 一般我们直接用优先队列实现堆: // 小顶堆,以 Integer 为例 PriorityQueue<Integer> pq = new PriorityQueue<>(); // 大顶堆 PriorityQueue<Int

每天更新大数据面经和技术 2520

堆的实现(偷懒版)

在上期内容介绍了二叉树、还简单提了一下堆的概念和大堆、小堆。回顾一下堆是首先是完全二叉树,因为是完全二叉树所以使用数组储存比较合理。本节重点堆的向上、向下调整算法的代码实现 和 复杂度计算。

2302_80867815的博客 1139

【数据结构】堆(堆的实现 堆向下调整算法 堆的创建 堆的插入 堆的删除 堆的代码实现 堆的应用)

给出一个数组,逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们把它构建成一个堆。删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。建堆和堆删除中都用到了向下调整,因此必须掌握了向下调整。先插入一个数组的尾上,再进行向上调整算法,直到满足堆。开始调整,一直调整到根节点的树,就可以调整成堆。1.将堆顶元素与堆中追后一个元素进行交换。堆是一个完全二叉树:分为小根堆和大根堆。大根堆:任何一个节点的值都>=孩子的值。前提:左右子树必须是一个堆,才能调整。

m0_68800221的博客 565

java 堆实现

java 堆实现 代码 public static void buildheap(int[] a , int heaplength) { for(int i = a.length/2-1; i >= 0; i–) { heapify(a, i, heaplength); } } //小顶堆 public static void heapify(int a[], int i, int hea...

qq_42716518的博客 317

堆实现

#include "heap.h" #include <malloc.h> #include <assert.h> #include <stdlib.h> #include <stdio.h> void Swap(HPDataType* pLeft, HPDataType* pRight){ HPDataType temp = *pLeft; ...

Shroudy的博客 316

数据结构-堆的实现

堆的实现 Heap.h 堆的管理及接口 #include<stdio.h> #include<stdlib.h> #include<assert.h> typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }Heap; //堆的向下调整算法 void AdjustDown(HPDataType* a, int n, int root); //堆的向

HanMeng的博客 7949

堆的实现算法

堆的实现算法 代码: #include #include #include <Windows.h> using namespace std; #define DEFAULT_CAPACITY 128 //堆的生成 typedef struct _Heap { int *arr; int size; int acpacity; }_Heap; //堆的接口 bool heapInit...

qq_39752645的博客 245

用数组实现堆

一、简单了解堆        如果有一个关键码的集合k = {k0,k1,k2,...,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:ki &lt;= k21+1 且 ki &lt;= k2i+2(ki &gt;= k2i+1 且 ki &gt;= k2i+2) i = 0,1,2...,则称为小堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆...

Sun_student的博客 3122

数据结构——堆的实现

堆的创建,插入,删除,还有YOP-K问题

plj521的博客 1380
上一篇: 622.设计循环队列
下一篇: 向上/向下调整建堆的区别
shylyly_
博客等级 码龄4年 1374粉丝 201原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值