【数据结构】详解堆

一、堆的概念

堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵 完全二叉树的 数组对象。 堆是非线性数据结构,相当于一维数组,有两个直接后继。
如果有一个关键码的集合K = { k₀,k₁,k₂ ,k₃ ,…,kₙ₋₁  },把它的所有元素按完全二叉树的顺序存储方式存储,在一个一维数组中,并满足:Kᵢ  <= K₂ *ᵢ₊₁  且 Kᵢ  <= K₂ *ᵢ₊₂  (Kᵢ  >= K₂ *ᵢ₊ ₁ 且 Kᵢ  >= K₂ *ᵢ₊₂ ) i = 0,1,2…,则称为小堆 (或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

【大根堆和小根堆】:

根结点最大的堆叫做大根堆,树中所有父亲都大于或等于孩子。

根结点最小的堆叫做小根堆,树中所有父亲都小于或等于孩子。

共同特点:父亲 =(孩子-1)/2

大堆小堆有什么特点呢?

我们购物平台中,我想选择销量大的前k家,这个时候,我们不需要对所有的数据进行排序,只需要取出前k家最大的值就可以。而最值常常出现在0号位,我们就可以利用Topheap解决,大大减少了我们的时间复杂度;

特点:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树
  • 每层节点个数为2^(h-1)个

二、堆的创建

1、头文件的声明:

typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}Heap;void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);void swap(HPDataType* p1, HPDataType* p2);void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

2、代码实现:

2.1堆的初始化与堆的摧毁

//堆的初始化
void HeapInit(Heap* hp) {assert(hp);hp->a = NULL;hp->capacity = hp->size = 0;
}//堆的摧毁
void HeapDestory(Heap* hp) {assert(hp);free(hp->a);hp->a = NULL;hp->capacity = hp->size = 0;
}

2.2堆的插入

下面给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的 向下调整算法 可以把它调整成一个小堆 。向下调整算法有一个前提:左右子树必须是一个堆( 包括大堆和小堆) ,才能调整。

具体步骤如下:

1.将新插入的元素放置在堆的最后一个位置(通常是数组的末尾)。

2.将该元素与其父节点进行比较。

3.若该元素大于(或小于,具体根据堆是最大堆还是最小堆而定)其父节点的值,则交换该元素和其父节点的位置。 

4.继续向上对比和交换,直到满足堆的性质或达到堆的根节点。

// 堆的插入
void HeapPush(Heap* hp, HPDataType x) {assert(hp);//与顺序表的开辟类似if (hp->size == hp->capacity){int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;//使用三目操作符开辟空间大小HPDataType* tmp = (HPDataType*)realloc(hp->a, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");return;}hp->a = tmp;hp->capacity = newcapacity;}hp->size++;hp->a[hp->size] = x;
//向上调整法,因为每次的插入是在数组末尾
//每次插入需要与父亲比较大小交换AdjustUp(hp->a, hp->size - 1);
}
2.2.1向上调整法 

//向上调整法,因为每次的插入是在数组末尾
//每次插入需要与父亲比较大小交换
    AdjustUp(hp->a, hp->size - 1);

我们每次插入末尾的位置,相当于孩子,我们需要找到该孩子的父亲与之比较大小,这个时候就要利用堆的特点:父亲 =(孩子-1)/2
 向上调整法:

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;parent = (child - 1) / 2;}else{break;}}
}

 交换函数:

因为在堆的实现中我们会经常使用父子交换

void swap(HPDataType* p1, HPDataType* p2) {HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;//这里也可以回想前面学习//不使用第三个变量,利用换位与实现交换
}

2.4堆的删除

如果我们直接删除堆顶数据,将数组数据整体向前移动,这样会导致堆的乱序;

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法

2.4.1向下调整法

void AdjustDown(HPDataType* a, int n, int parent) {//注:这里的parent为0,而数组a则是首尾交换的int child = parent * 2 + 1;//假设左孩子小while (child < n) {//while循环直到超出数组长度//左孩子大if (a[child] > a[child + 1]) {child++;}if (a[child]>a[parent]) {swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}}
}

这样我们只需要完成交换,传指就可以了

void HeapPop(Heap* hp) {assert(hp);assert(hp->size);swap(&hp->a[0], &hp->a[hp->size - 1]);hp->size--;AdjustDown(hp->a, hp->size, 0);
}

3、总结

升序:建大堆

降序:建小堆

(1)升序:建大堆

【思考】排升序,建小堆可以吗?-- 可以(但不推荐)。

首先对 n 个数建小堆,选出最小的数,接着对剩下的 n-1 个数建小堆,选出第二小的数,不断重复上述过程。

【时间复杂度】建 n 个数的堆时间复杂度是 O(N),所以上述操作时间复杂度为 O(N²),效率太低,关系变得杂乱,尤其是当数据量大的时候,效率就更低。同时堆的价值也没有被体现出来,这样不如用直接排序。

排升序,因为数字依次递增,需要找到最大的数字,得建大堆。

首先对 n 个数建大堆。将最大的数(堆顶)和最后一个数交换,把最大的数放到最后。前面 n-1 个数的堆结构没有被破坏(最后一个数不看作在堆里面的),根节点的左右子树依然是大堆,所以我们进行一次向下调整成大堆即可选出第 2 大的数,放到倒数第二个位置,然后重复上述步骤。

【时间复杂度】:建堆时间复杂度为 O(N),向下调整时间复杂度为 O(log₂N),这里我们最多进行N-2 次向下调整,所以堆排序时间复杂度为 O(N*log₂N),效率相较而言是很高的。

因为在堆的实现中我们会经常使用父子交换

void swap(HPDataType* p1, HPDataType* p2) {HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;//这里也可以回想前面学习//不使用第三个变量,利用换位与实现交换
}

我相信接下来对你来说简直轻而易举

// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

4.1取堆顶数据

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

4.2 堆的数据个数

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

4.3堆的判空

int HeapEmpty(Heap* hp) {assert(hp);assert(hp->size>0);//为NULL,返回1,不为NULL,返回0;return hp->size == 0 ? 1 : 0;
}

5、堆的时间复杂度

5.1建堆的时间复杂度

5.1.1向下调整法建堆
// 堆的插入
void HeapPush(Heap* hp, HPDataType x) {assert(hp);//与顺序表的开辟类似if (hp->size == hp->capacity){int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;//使用三目操作符开辟空间大小HPDataType* tmp = (HPDataType*)realloc(hp->a, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");return;}hp->a = tmp;hp->capacity = newcapacity;}hp->size++;hp->a[hp->size] = x;
//向下调整法,因为每次的插入是在数组末尾
//每次插入需要与父亲比较大小交换AdjustUp(hp->a, hp->size - 1);
}

  因此:建堆的时间复杂度为O(N)

等比数列求和公式: 
建堆要从倒数第一个非叶子节点开始调整,也即是从倒数第二层开始调,可得出时间复杂度公式:T ( n ) = ∑ ( 每 层 节 点 数 ∗ ( 堆 的 高 度 − 当 前 层 数 ) ) 
建堆的时间复杂度为O(N)。(向下调整算法)

为何使用向下调整建堆而不使用向上调整建堆

5.1.2向上调整法建堆

可以看出结点数多的层, 调整次数也多, 结点数少的层, 调整次数少, 时间复杂度为O(N*logN), 所以一般建堆都采用向下调整建堆法. 

6、TOPK问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大

比如:专业前十、世界500强、销量最高的前十、富豪榜等等

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能

数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

1. 用数据集合中前K个元素来建堆

前k个最大的元素,则建小堆

前k个最小的元素,则建大堆

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

 我们常常会想到冒泡排序( O(N^2) )
对于少量的数据是可以拿捏的,但面对100000的数据就显得有点吃力,而堆排序O(N*logN)则觉得轻而易举

 

7、堆排序  

void HeapSort(int* a, int n)
{// 降序,建小堆// 升序,建大堆// 向上调整建堆 O(N*logN)/*for (int i = 1; i < n; i++){AdjustUp(a, i);}*/// 向下调整建堆 O(N)for (int i = (n-1-1)/2; i >= 0; i--){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

哇呜!点个赞走吧!!!

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

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

相关文章

PCB(印制电路板)制造涉及的常规设备

印制电路板&#xff08;PCB&#xff09;的制造涉及多种设备和工艺。从设计、制作原型到批量生产&#xff0c;每个阶段都需要不同的专业设备。以下是一些在PCB制造过程中常见的设备&#xff1a; 1. 计算机辅助设计&#xff08;CAD&#xff09;软件&#xff1a; - 用于设计PC…

3D问界—MAYA制作铁丝栅栏(透明贴图法)

当然&#xff0c;如果想通过建立模型法来实现铁丝栅栏的效果&#xff0c;也不是不行&#xff0c;可以找一下栅栏建模教程。本篇文章主要是记录一下如何使用透明贴图来实现创建铁丝栅栏&#xff0c;主要应用于场景建模&#xff0c;比如游戏场景、建筑场景等大环境&#xff0c;不…

问题清除指南|成功解决pipmatplotlib因为ConnectTimeoutError更新失败问题

前言&#xff1a;跑baseline需要升级matplotlib和pip&#xff0c;在此记录一个错误和一个「别致」的解决方案。 北京时间 14:00 左右&#xff0c;在终端环境中运行命令python -m pip install --upgrade pip&#xff0c;报错&#xff1a; 多次尝试&#xff0c;未果。 隔天上午 0…

Qt支持LG高级汽车内容平台

Qt Group与LG 电子&#xff08;简称LG&#xff09;正携手合作&#xff0c;将Qt软件框架嵌入其基于 webOS的ACPLG车载娱乐平台&#xff0c;用于应用程序开发。该合作旨在让原始设备制造商&#xff08;OEM&#xff09;的开发者和设计师能为汽车创建更具创新性的沉浸式汽车内容流媒…

1-2、truffle与webjs亲密接触(truffle智能合约项目实战)

1-2、truffle与webjs亲密接触&#xff08;truffle智能合约项目实战&#xff09; 5&#xff0c;web3调用智能合约6&#xff0c;Ganache 5&#xff0c;web3调用智能合约 在前面已经完成简单的合约编写 使用web3调用此函数 Web端的代码使用web3进行智能合约的访问 首先在cmd以…

SCI一区级 | Matlab实现SSA-CNN-GRU-Multihead-Attention多变量时间序列预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.【SCI一区级】Matlab实现SSA-CNN-GRU-Multihead-Attention麻雀算法优化卷积门控循环单元融合多头注意力机制多变量时间序列预测&#xff0c;要求Matlab2023版以上&#xff1b; 2.输入多个特征&#xff0c;输出单个…

数据结构之细说链表

1.1顺序表的问题以及思考 经过上一篇顺序表的学习&#xff0c;我们知道顺序表还是有很多缺点 顺序表的缺点&#xff1a; 1.中间/头部的插入删除&#xff0c;实际复杂度为O(N) 2.增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间。会有不小的消耗 3.扩容一般…

R语言包AMORE安装报错问题以及RStudio与Rtools环境配置

在使用R语言进行AMORE安装时会遇到报错&#xff0c;这时候需要采用解决办法&#xff1a; AMORE包安装&#xff0c;需要离线官网下载安装包&#xff1a; Index of /src/contrib/Archive/AMORE (r-project.org)https://cran.r-project.org/src/contrib/Archive/AMORE/ 一、出现…

AI绘画入门实践|Midjourney 的模型版本

模型分类 Midjourney 的模型主要分为2大类&#xff1a; 默认模型&#xff1a;目前包括&#xff1a;V1, V2, V3, V4, V5.0, V5.1, V5.2, V6 NIJI模型&#xff1a;目前包括&#xff1a;NIJI V4, NIJI V5, NIJI V6 模型切换 你在服务器输入框中输入 /settings&#xff1a; 回车后…

DETR算法解读——Transformer在目标检测任务的首次应用

论文&#xff1a;End-to-End Object Detection with Transformers 作者&#xff1a;Nicolas Carion, Francisco Massa, Gabriel Synnaeve, Nicolas Usunier, Alexander Kirillov, Sergey Zagoruyko 机构&#xff1a;Facebook AI 链接&#xff1a;https://arxiv.org/abs/2005.12…

【STL详解 —— map和set的使用】

STL详解 —— map和set的使用 关联式容器键值对setset的介绍set的使用set的模板参数列表set的构造set的迭代器set的容量set的修改操作 mapmap的介绍map的使用map的模板参数列表map的构造map的迭代器map的容量与元素访问map中元素的修改 multisetmultimap 关联式容器 在初阶阶段…

Camera Raw:首选项

Camera Raw 首选项 Preferences提供了丰富的配置选项&#xff0c;通过合理设置&#xff0c;可以显著提升图像处理的效率和效果。根据个人需求调整这些选项&#xff0c;有助于创建理想的工作环境和输出质量。 ◆ ◆ ◆ 打开 Camera Raw 首选项 方法一&#xff1a;在 Adobe Bri…

纯硬件一键开关机电路的工作原理

这是一个一键开关机电路: 当按一下按键然后松开&#xff0c;MOS管导通&#xff0c;VOUT等于电源电压; 当再次按一下按键然后松开&#xff0c;MOS管关闭&#xff0c;VOUT等于0; 下面来分析一下这个电路的工作原理。上电后&#xff0c;输入电压通过R1和R2给电容充电&#xff0c;最…

微软GraphRAG +本地模型+Gradio 简单测试笔记

安装 pip install graphragmkdir -p ./ragtest/input#将文档拷贝至 ./ragtest/input/ 下python -m graphrag.index --init --root ./ragtest修改settings.yaml encoding_model: cl100k_base skip_workflows: [] llm:api_key: ${GRAPHRAG_API_KEY}type: openai_chat # or azu…

项目管理进阶之RACI矩阵

前言 项目管理进阶系列续新篇。 RACI&#xff1f;这个是什么矩阵&#xff0c;有什么用途&#xff1f; 在项目管理过程中&#xff0c;如Team规模超5以上时&#xff0c;则有必要采用科学的管理方式&#xff0c;满足工作需要。否则可能事倍功半。 Q&#xff1a;什么是RACI矩阵 …

分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法

前言 树形结构是一种很常见的数据结构&#xff0c;类似于现实生活中的树的结构&#xff0c;具有根节点、父子关系和层级结构。 所谓根节点&#xff0c;就是整个树的起始节点。 节点则是树中的元素&#xff0c;每个节点可以有零个或多个子节点&#xff0c;节点按照层级排列&a…

STM32 IAP 需要关注的一些事

1、首先要知道STM32的程序是如何分布在FLASH中的。 2、升级的时候涉及到两个程序&#xff0c;一个是bootloader&#xff0c;一个是user程序&#xff0c;这两个程序的功能分别的什么作用的&#xff1f; 3、编译的固件是怎么分布的&#xff1f;通过那个配置文件去指导编译器去排布…

内网对抗-隧道技术篇防火墙组策略ICMPDNSSMB协议出网判断C2上线解决方案

知识点&#xff1a; 1、隧道技术篇-网络层-ICMP协议-判断&封装&建立&穿透 2、隧道技术篇-传输层-DNS协议-判断&封装&建立&穿透 3、隧道技术篇-表示层-SMB协议-判断&封装&建立&穿透0、不是有互联网才叫出网 1、C2常见上线采用的协议 2、常…

IDEA 调试 Ja-Netfilter

首先本地需要有两款IDEA 可以是相同版本&#xff0c;也可以是不同版本。反正要有两个&#xff0c;一个用来调试代码&#xff0c;一个启动。 移除原有ja-netfiler 打开你的ja-netfiler的vmoptions目录&#xff0c;修改其中的idea.vmoptions文件。移除最后一行-javaagent ...参…

基于R语言BIOMOD2 及机器学习方法的物种分布模拟

BIOMOD2是一个R软件包&#xff0c;用于构建和评估物种分布模型&#xff08;SDMs&#xff09;。它集成了多种统计和机器学习方法&#xff0c;如GLM、GAM、SVM等&#xff0c;允许用户预测和分析物种在不同环境条件下的地理分布。通过这种方式&#xff0c;BIOMOD帮助研究者评估气候…