椋鸟数据结构笔记#6:堆及其实现

news2024/10/9 20:23:28

文章目录

      • 堆的概念
      • 堆的实现
        • 由数组调整为堆
          • 堆向下调整算法
          • 通过向下调整算法构建堆
        • 从空堆开始插入节点
          • 堆向上调整算法
          • 通过向上调整算法构建堆
        • 删除堆顶的元素
        • 实现代码
      • 堆的作用

萌新的学习笔记,写错了恳请斧正。

堆的概念

如果有一个关键码的集合 K = { k 0 , k 1 , k 2 , . . . , k n − 1 } K = \lbrace k0,k1,k2,...,k_{n-1} \rbrace K={k0,k1,k2,...,kn1} ,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: K i ≤ K 2 i + 1 K_i \le K_{2i+1} KiK2i+1 K i ≤ K 2 i + 2 K_i \le K_{2i+2} KiK2i+2 K i ≥ K 2 i + 1 K_i \ge K_{2i+1} KiK2i+1 K i ≥ K 2 i + 2 K_i \ge K_{2i+2} KiK2i+2), i = 0 , 1 , 2 , . . . i = 0,1,2,... i=0,1,2,...,则称为小堆(大堆)。

上面这段定义似乎有些难以理解,但其实非常简单,堆就是一个完全二叉树,但是要满足所有子节点都小于其父节点或者所有父节点都小于其子节点。小堆也叫小根堆(根节点最小),大堆也叫大根堆(根节点最大)。

下面是大根堆和小根堆的两张示意图:

大根堆
小根堆

在这两张示意图中,大根堆的存储数组为[9,6,8,5,3,4,7,2,1],小根堆是[1,3,4,4,5,8,7,9,6]

堆的实现

我们想要构建一个堆,一般是给出一个数组,然后调整其元素顺序变成堆;或者是从一个空堆开始逐步插入元素构建堆,这里就涉及到堆的调整算法

对于不同的应用需求,我们可以选择向上调整构建堆或者向下调整构建堆,下面我们来详细说明这两者的区别。

由数组调整为堆

由数组调整为堆我们一般选择通过向下调整算法来构建堆

堆向下调整算法

堆向下调整算法可以用来调整某一个元素的位置。

大根堆的向下调整:不断的将某个选定的节点与其两个子节点比较,如果子节点有比该节点大的,就将子节点中更大的那一个与该节点交换,然后反复重复这个过程直到该节点不能继续调整。

小根堆的向下调整:不断的将某个选定的节点与其两个子节点比较,如果子节点有比该节点小的,就将子节点中更小的那一个与该节点交换,然后反复重复这个过程直到该节点不能继续调整。

通过向下调整算法构建堆
  1. 找到倒数第一个非叶子节点:在一个完全二叉树中,如果节点总数为 N N N,则倒数第一个非叶子节点的位置是 ⌊ N / 2 ⌋ \lfloor N/2 \rfloor N/2。这个节点之后的所有节点都是叶子节点,它们自然满足堆的性质。
  2. 从该非叶子节点开始向下调整:对于每个非叶子节点,应用向下调整算法,确保当前节点以及其子树满足堆的性质。
  3. 逆序遍历所有非叶子节点:继续向上,对每个非叶子节点重复进行向下调整的操作,直到根节点也被调整完成。

这个过程被称为“堆化”,看起来很复杂,但其实效率非常非常高

我们可以这样分析:

  • 树的最底层拥有约𝑁/2N/2个节点,但这些节点都是叶子节点,不需要进行向下调整。
  • 上一层拥有约𝑁/4N/4个节点,每个节点最多进行1次比较。
  • 再上一层拥有约𝑁/8N/8个节点,每个节点最多进行2次比较。
  • 依此类推,直到根节点,每层的节点数大约是上一层的一半,而每个节点需要的比较次数大约是其所在层的深度。
  • 将这个过程的比较次数加总起来,会发现总的比较次数是 O ( N ) O(N) O(N)

也就是说这样构建一个堆的时间复杂度仅为 O ( N ) O(N) O(N)

从空堆开始插入节点

从空堆开始插入节点,我们一般选择通过向上调整算法来构建堆

堆向上调整算法

堆的向上调整算法与向下调整算法相反,是比较当前节点与其父节点并在必要时交换。

大根堆的向上调整:将当前节点的值与其父节点的值进行比较,如果当前节点的值大于其父节点的值,则需要交换这两个节点,重复这个过程。

小根堆的向上调整:将当前节点的值与其父节点的值进行比较,如果当前节点的值小于其父节点的值,则需要交换这两个节点,重复这个过程。

通过向上调整算法构建堆
  1. 逐个添加元素:从空堆开始,逐个将新元素添加到堆的末尾,即堆的下一个可用位置,以保持完全二叉树的结构。
  2. 向上调整:对于每个新添加的元素,应用向上调整算法,以确保新元素的添加不会破坏堆的性质。新元素与其父节点比较,并在需要时进行交换,这个过程一直持续到达堆的顶部或不需要进一步交换为止。
  3. 重复直到所有元素添加完毕:重复上述过程,直到所有元素都被添加到堆中,且整个堆满足最大堆或最小堆的性质。

该算法每次插入的时间复杂度是 O ( l o g N ) O(logN) O(logN),因为每次插入后的向上调整最多需要从堆的底部调整到顶部,即跨越堆的高度,堆的高度为 O ( l o g N ) O(logN) O(logN)。但是并不能直接将它们相乘,因为这里的N是动态变化的,是从1开始逐渐增加到N,那么我们就能得到如下计算式:

∑ i = 1 n O ( log ⁡ i ) = O ( log ⁡ 1 + log ⁡ 2 + log ⁡ 3 + . . . + log ⁡ N ) = O ( log ⁡ ( 1 × 2 × 3 × . . . × N ) ) = O ( log ⁡ ( N ! ) ) \begin {array}{c} \sum^{n}_{i=1}O(\log i) = O(\log 1 + \log 2 + \log 3 + ... + \log N) \\ = O(\log (1\times2\times3\times...\times N)) \\ = O(\log (N!)) \end {array} i=1nO(logi)=O(log1+log2+log3+...+logN)=O(log(1×2×3×...×N))=O(log(N!))

由根据斯特林近似,我们知道:

O ( log ⁡ ( N ! ) ) ≈ N log ⁡ N − N \begin {array}{c} O(\log (N!)) \approx N\log N - N \end {array} O(log(N!))NlogNN

那么其时间复杂度就是 O ( N log ⁡ N ) O(N\log N) O(NlogN)

删除堆顶的元素

如果我们想要删除堆中的一个元素,我们当然不能直接像数组那样把后面的元素往前挪,那我们应该怎么做呢?

  1. 交换元素:将堆顶元素与堆最后一个元素交换
  2. 删除数据:将原堆顶元素删除
  3. 向下调整:对新的堆顶执行向下调整操作

这样我们就得到了删去堆顶元素的新堆啦~

实现代码
#pragma once

#include <stdlib.h>
#include <string.h>
#include <assert.h>

//#define MIN_HEAP
#define MAX_HEAP

#ifdef MIN_HEAP
	#define HEAP_COMPARE(a, b) ((a) < (b))
#endif // MIN_HEAP
#ifdef MAX_HEAP
	#define HEAP_COMPARE(a, b) ((a) > (b))
#endif // MAX_HEAP

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* data;
	int size;
	int capacity;
} Heap;

void HeapInit(Heap* php);
void HeapInitArr(Heap* php, HPDataType* a, int n);
void HeapDestory(Heap* php);
void HeapPush(Heap* php, HPDataType x);
HPDataType HeapTop(Heap* php);
void HeapPop(Heap* php);	//删除堆顶元素
int HeapSize(Heap* php);
int HeapEmpty(Heap* php);

void Swap(HPDataType* a, HPDataType* b);
void AdjustDown(HPDataType* data, int size, int parent);
void AdjustUp(HPDataType* data, int child);
#define _CRT_SECURE_NO_WARNINGS

#include "Heap.h"

void HeapInit(Heap* php)
{
	assert(php);
	php->data = NULL;
	php->size = 0;
	php->capacity = 0;
}

void HeapInitArr(Heap* php, HPDataType* a, int n)
{
	assert(php);
	php->data = (HPDataType*)malloc(n * sizeof(HPDataType));
	if (php->data == NULL)
	{
		perror("malloc fail\n");
		exit(EXIT_FAILURE);
	}
	memcpy(php->data, a, n * sizeof(HPDataType));
	php->size = php->capacity = n;

	//向下调整
	for (int i = (n - 2) / 2; i >= 0; --i)
	{
		AdjustDown(php->data, php->size, i);
	}
}

void HeapDestory(Heap* php)
{
	assert(php);
	free(php->data);
	php->data = NULL;
	php->size = php->capacity = 0;
}

void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

void AdjustUp(HPDataType* data, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (HEAP_COMPARE(data[child], data[parent]))
		{
			Swap(&data[child], &data[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	
	//扩容
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 6 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->data, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail\n");
			exit(EXIT_FAILURE);
		}
		php->data = tmp;
		php->capacity = newcapacity;
	}

	//尾插
	php->data[php->size++] = x;

	//向上调整
	AdjustUp(php->data, php->size-1);
}

HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(php->size > 0);
	return php->data[0];
}

void AdjustDown(HPDataType* data, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && HEAP_COMPARE(data[child + 1], data[child]))
		{
			++child;
		}
		if (HEAP_COMPARE(data[child], data[parent]))
		{
			Swap(&data[child], &data[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(Heap* php)
{
	assert(php);
	assert(php->size > 0);
	Swap(&php->data[0], &php->data[php->size - 1]);
	--php->size;
	AdjustDown(php->data, php->size, 0);
}

int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

int HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

堆的作用

堆可以用于堆排序和TopK问题,具体的会在下一篇笔记讲解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1568270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于java的电影院售票网站

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclip…

Doris实践——同程数科实时数仓建设

目录 前言 一、早期架构演进 二、Doris和Clickhouse选型对比 三、新一代统一实时数据仓库 四、基于Doris的一站式数据平台 4.1 一键生成任务脚本提升任务开发效率 4.2 自动调度监控保障任务正常运行 4.3 安全便捷的可视化查询分析 4.4 完备智能的集群监控 五、收益与…

网络与并发编程(一)

并发编程介绍_串行_并行_并发的区别 串行、并行与并发的区别 串行(serial)&#xff1a;一个CPU上&#xff0c;按顺序完成多个任务并行(parallelism)&#xff1a;指的是任务数小于等于cpu核数&#xff0c;即任务真的是一起执行的并发(concurrency)&#xff1a;一个CPU采用时间…

Java 变得越来越像 Rust?

随着编程技术的增强和复杂性的提升&#xff0c;许多编程语言也纷纷效仿&#xff0c;Java 也不例外。 另一边&#xff0c;尽管社区内部问题重重&#xff0c;但 Rust 仍逐年获得开发人员的喜爱。这背后都是有原因的&#xff1a;Rust 的编译器让开发人员避免了各种问题。编译器对…

Spring的BeanFactory和FactoryBean有什么区别?

两者的区别 BeanFactory定义了ioc容器的最基本形式,并提供了ioc容器应遵守的的最基本的接口,也就是Spring ioc所遵守的最底层和最基本的编程规范,它只是个接口,并不是ioc容器的具体实现。它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。再来说说…

jvisualvm 使用教程

之前看过 jvisualvm&#xff0c;但是那个时候对 JVM 并不是很熟悉&#xff0c;后面看了下八股文&#xff0c;看了下 JVM 的相关知识之后&#xff0c;发现多了解点 JVM 的东西&#xff0c;对我们 CRUD 其实是有指导意义的&#xff0c;就比如我们通常会 new 一堆的没有用到的对象…

Vue项目+ 打包解决静态资源无法加载和路由加载无效(404)问题

vue项目npm run build打包发到服务器上&#xff0c;背景图片消失 问题 登录页背景图片丢失 控制台报错 找到我们的 config文件夹下面的 index.js 配置文件找到其中的 build 相关配置&#xff0c;assetsPublicPath 这一项默认配置的是‘/’ 我们将他改成 ‘./’ 修改后 修…

【数据结构】初识数据结构与复杂度总结

前言 C语言这块算是总结完了&#xff0c;那从本篇开始就是步入一个新的大章——数据结构&#xff0c;这篇我们先来认识一下数据结构有关知识&#xff0c;以及复杂度的相关知识 个人主页&#xff1a;小张同学zkf 若有问题 评论区见 感兴趣就关注一下吧 目录 1.什么是数据结构 2.…

k8s 部署 canal 集群,RocketMQ 模式

k8s 部署 canal 集群&#xff0c;RocketMQ 模式 k8s 部署 canal 集群&#xff0c;RocketMQ 模式前提MySQLRocketMQ制作 canal-admin、canal-server 镜像 部署 zookeeper部署 canal-admin部署 canal-server测试 k8s 部署 canal 集群&#xff0c;RocketMQ 模式 前提 MySQL 开启…

Excel制作甘特图

使用Excel表格制作甘特图&#xff0c;可根据任务开始时间和结束时间自动计算工时&#xff0c;并自动用指定颜色填充横道图。 1.新建Excel文档&#xff0c;先设置项目基本信息&#xff0c;包括表格名称&#xff0c;这里设置为“**项目甘特图”&#xff1b;然后添加任务序号列&a…

移动端WEB开发之响应式布局

一、响应式开发 1.1 响应式开发原理 就是使用媒体查询针对不同宽度的设备进行布局和样式的设置&#xff0c;从而适配不同设备的目的。 1.2 响应式布局容器 响应式需要一个父级做为布局容器&#xff0c;来配合子级元素来实现变化效果。原理就是在不同屏幕下&#xff0c;通过媒体…

机器狗首次阵亡!美国警方披露详情

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 那天&#xff0c;唯一的伤亡者是我们的机器狗。 美国警察最新公布一则案件&#xff1a;波士顿…

python set是什么类型

python set是一种数据类型&#xff0c;数学里的集合概念&#xff0c;在Python语言里对应的是set类型。与list&#xff0c;tuple不同的地方是&#xff0c;set更加强调的是一种“从属关系”&#xff08;membership&#xff09;&#xff0c;跟顺序无关&#xff0c;所以有重复的元素…

达梦数据库 索引管理

索引的基本认识 索引是为了快速检索和定位数据行而创建的一种数据结构。索引是由表中索引列数据进行排序后的集合和指向这些值的物理标识&#xff08;例如&#xff1a;ROWID 等聚集索引键&#xff09;共同组成。在 DM 中&#xff0c;除了位图索引、位图连接索引、全文索引和空…

代码随想录算法训练营第二十九天(回溯5)|491. 非递减子序列、46. 全排列、47. 全排列 II(JAVA)

文章目录 491. 非递减子序列解题思路源码 46. 全排列解题思路源码 47. 全排列 II解题思路源码 总结 491. 非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 …

练手项目层初阶1—《详解静态版本——通讯录管理系统》

文章目录 &#x1f6a9;前言&#x1f50a; 项目需求&#x1f4da; 项目知识点包含&#x1f9e9;项目框架&#x1f4dd;框架拆解分析✨Struct_book.h 头文件解析✨Struct_book.c文件解析✨test_book.c文件解析 &#x1f4fa;演示效果&#x1f680;完整代码 &#x1f6a9;前言 俗…

Linux下Qt生成程序崩溃文件

文章目录 1.背景2.Qt编译生成程序2.1.profile模式的本质 3.执行程序&#xff0c;得到core文件4.代码定位4.1.直接使用gdb4.2.使用QtCreator 5.总结6.题外话6.1.profile模式和debug模式的区别 1.背景 在使用Qt时&#xff0c;假如在windows&#xff0c;当软件崩溃时&#xff0c;…

Linux 服务器间SSH免密码登录与拷贝文件(SCP)

适用背景 工作中频繁登录服务器和拷贝文件&#xff0c;都会提示输入密码在持续集成的场景下&#xff0c;自动部署应用时是没有人工干预的 解决方案 下面以实现A服务器到B服务器的免密码登录和拷贝文件为例&#xff0c;介绍相关的配置。&#xff08;即A访问B不需要输密码&…

不能在主机和虚拟机之间拷贝文本(虚拟机ubuntu16.04)

问题 ubuntu16.04不能在主机和虚拟机之间拷贝文本。 原因 vmware tools没安装好。 解决办法 重新安装vmware tools&#xff0c;步骤入下&#xff1a; 让虚拟机加载C:\Program Files (x86)\VMware\VMware Workstation\linux.iso光盘文件&#xff0c;设置如下&#xff1a; …

DHT11温湿度传感器使用视频教程分享

下载地址&#xff1a; 温湿度计(STCDHT11): https://url83.ctfile.com/d/45573183-60623983-9b7f6c?p7526 (访问密码: 7526)