堆的结构实现与应用

目录

         前言:

1.认识堆

a.如何认识堆?

b.大根堆与小根堆

c.堆应用的简单认识

2.堆的结构与要实现的功能

3.向上调整算法

4.向下调整算法

5.向堆插入数据并建堆

6.堆的大小

7.堆的判空

8.取堆顶数据

9.删除堆顶数据

10.向上调整时间复杂度

11.向下调整时间复杂度

12.堆排序

a.直接将数组放到堆再取堆顶

b.在将数组放到堆的时候就直接调整,用数组建堆

13.topk问题

总结:


前言:

堆其实与二叉树息息相关,本篇将从如何实现堆,以及堆的应用等方面入手。

1.认识堆

a.如何认识堆?

我们只要记住关键的两点:1.堆必须是完全二叉树。2.堆要么是大堆,要么是小堆。

b.大根堆与小根堆

那什么是大堆,什么又是小堆呢?

大堆:树中任意一个父亲都大于或等于孩子。

小堆:树中任意一个父亲都小于或等于孩子。

c.堆应用的简单认识

堆排序:时间复杂度为O(N*logN),属于快一点的排序。

topk问题:N个数找最大的前K个。

优先级队列:C++中stl的priority_queue容器的底层实现需要用到建堆的思想。

2.堆的结构与要实现的功能

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;HPDataType size;HPDataType capacity;
}HP;void InitHeap(HP* php);
void DestroyHeap(HP* php);
void PushHeap(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
int HeapSize(HP* php);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(int* a, int n, int parent);
void Swap(HPDataType* p1, HPDataType* p2);

我们提供以下数据来建堆:

3.向上调整算法

我们现在如果要让提供的数据插入到这个堆里面,如何保证插入的时候就建好堆了呢?这时我们就要用到向上向下调整的算法了。假设我们现在要建一个小堆,就要保证每一个子节点都要小于或者等于它的父节点,用我们提供好的数据,当插入到32这个数据的时候就要进行调整了:

既然是向上调整,那我们就要找孩子的父亲,那如何找到父亲呢?通过下标的关系可以发现,不管是左还是右孩子,只要遵循(child-1)/2就能找到父亲的下标,然后就是交换嘛;交换过后我们要让孩子走到父亲位置,再找到新的父亲,一轮一轮的向上,这就是向上调整算法:

void AdjustUp(HPDataType* a, int child)
{//默认建小堆int parent = (child - 1) / 2;while (child > 0)//等于0就停止了,等于0说明孩子在根的位置,就没有父亲了{if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;//孩子走到父亲的位置继续再找新的父亲parent = (child - 1) / 2;}else{break;}}

要注意的就是循环条件,如果孩子走到0说明走到根的位置了,就没有父亲了,循环停止。

4.向下调整算法

向下调整算法与向上调整算法相反,其实向下就是找孩子,如果建小堆就让左右孩子中最小的孩子与父亲交换(最小的孩子与父亲交换后,父亲就变成三者中最小的了,符合小堆的性质),再让父亲走到孩子的位置上,再往下继续找到新的孩子,直到孩子不存在的情况。

关键来了,如何找到左右孩子中的最小的那个呢?如何通过父亲找到孩子呢?

我们可以默认左孩子是最小的那一个,如果左孩子大于右孩子,那做孩子的下标+1不就到右孩子了吗?解决了第一个问题,那如何通过父亲找到孩子呢,既然我们默认左孩子是小的那一个,我们可以先找到左孩子,通过下标的关系,我们就知道左孩子child=parent*2+1,好了,这就是向下调整算法的思路,来看代码:

void AdjustDown(int* a, int n, int parent)
{//默认小堆int child = parent * 2 + 1;//默认是左孩子while (child < n){//这里右孩子的存在条件必须放在&&的前面,因为如果放在后面,前面的条件为假,右孩子也为假,就判断不出来是哪个了(检查右孩子存在必须更严格)if (child + 1 < n && a[child] > a[child + 1])//如果右孩子存在(因为如果左孩子为n-1,那右孩子就为n了,就越界了)并且左孩子大于右孩子,下标就走到右孩子上{child = child + 1;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}

注意点:

1.我们既然默认左孩子为小的那一个,那结束条件就应该是左孩子不存在的情况即当左孩子等于n的时候就越界了,而又由于堆是完全二叉树,所以左孩子不存在,那右孩子一定不存在,所以只写这一个就行。

2.child + 1 < n && a[child] > a[child + 1],首先需要注意左孩子存在,但右孩子不存在的情况,所以判断child+1<n,其次这个条件要写到&&的前面,因为如果写到后面,a[child]>a[child+1]为假,就判断不出右孩子可能越界的情况了,所以右孩子的检查应该放到&&前面。

5.向堆插入数据并建堆

void PushHeap(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int NewCapacity = php->capacity = 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * NewCapacity);if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = NewCapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);//向上调整传孩子,即插入的数据,找父亲}

需要注意的是最后向上调整传的孩子是插入的数据的下标,因为插入后size++了,所以-1才对应插入的数据的下标。

6.堆的大小

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

7.堆的判空

bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}

8.取堆顶数据

HPDataType HeapTop(HP* php)
{assert(php);assert(!HeapEmpty(&php));return php->a[0];
}

9.删除堆顶数据

如果我们直接删除堆顶的数据会导致这个堆变乱,所以我们采用交换堆顶和堆尾的数据,将堆的大小减1,这样就访问不到堆尾的数据也就起到了删除的效果了,然后我们再从根节点开始做向下调整算法,恢复堆即可,注意空堆不能删,要判空:

void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(&php));Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);//从根节点开始向下调整}

10.向上调整时间复杂度

按最坏的情况计算时间复杂度,就拿满二叉树来说:

我们先找到每一次的节点数,再乘向上调整的次数,假设树的高度为h,那我们就将1-h层的所有节点的调整次数相加,就是时间复杂度(计算采用等比数列求和的乘公比错位相减的方法):

又因为满二叉树的节点个数为2^h-1,所以我们设树有N个节点,就能得到高度,再代入F(h):

实际去除不影响结果的项也就是O(N*logN),N为节点个数。

11.向下调整时间复杂度

一样拿满二叉树来说:

实际计算结果:

实际去除不影响结果的项也就是O(N),N为节点个数。

12.堆排序

a.直接将数组放到堆再取堆顶

void HeapSort1(int* a, int n)
{HP hp;InitHeap(&hp);for (int i = 0; i < n; i++){PushHeap(&hp, a[i]);}int i = 0;while (!HeapEmpty(&hp)){int top = HeapTop(&hp);a[i++] = top;HeapPop(&hp);}
}

这样的坏处就是想要改升序降序要改向上向下调整的逻辑,有些麻烦,而且时间上有些麻烦,需要堆排的时候还要写一个堆出来。

b.在将数组放到堆的时候就直接调整,用数组建堆

如果我们要排成降序,就建小堆,小堆选出最小的,首尾交换,最小的放到最后的位置,最后一个数据不看做堆里面的,再次向下调整就可以选出次小的,以此类推,相当于一个一个头插;

调用一次是O(logN),N次就是O(N*logN),计算方法跟向下调整差不多;

向下调整建堆需要倒着调整,叶子节点不需要处理,倒数第一个非叶子节点即最后一个节点的父亲开始调整:

void HeapSort(int* a, int n)
{for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

如果我们要排升序,只需要改动向下调整或者写两个建堆方法:

改动两处:选孩子时选大的那个;如果孩子大于父亲,交换:

建堆时也可向上调整建堆,具体实现博主暂时不清楚~~~:

for (int i = 1; i < n; i++)//下标为0即第一个数默认是堆{AdjustUp(a, i);//建堆,相当于一个一个插入成堆}

注意:

1.为什么升序不建小堆呢?因为小堆最小的已经在前面了,不管是移动还是怎么剩下的都要重新建堆

2.堆排序整体时间复杂度为N+N*logN,也就是O(N*logN)

13.topk问题

什么是topk问题?

就是N个数找最大的前N个:

面对庞大的数据,数据放在磁盘的文件里面,而内存是有限的,所以我们将这些数据的前k个建堆,将剩下的数据与堆顶元素进行比较,符合条件就交换,然后再调整,重复操作即可,那该怎么建堆呢?首先对前3 数据进行建一个小堆,注意这里不能建大堆,如果建大堆的话,可能最大的数据在前三个数,其余2个数据在余下的 N-K个数里面,这样其余2个就不能进堆了:

void CreateNData()
{//造数据int n = 10000;srand((unsigned int)time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (size_t i = 0; i < n; i++){int x = rand() % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}void PrintTopK(int k)
{const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}int* kminheap = (int*)malloc(sizeof(int) * k);if (kminheap == NULL){perror("malloc error");return;}for (int i = 0; i < k; i++){fscanf(fout, "%d", &kminheap[i]);}for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(kminheap, k, i);}int val = 0;while (!feof(fout))//fscanf读到文件结尾,调用feof,feof读到文件末尾返回非0,否则返回0{fscanf(fout, "%d", &val);if (val > kminheap[0]){kminheap[0] = val;AdjustDown(kminheap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", kminheap[i]);}printf("\n");
}

总结:

堆的结构不难,难在和其他的场景联系到一起并涉及一些算法,所以还是掌握好它结构和算法的基础为主

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

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

相关文章

rtsp推拉流

1.搭建视频服务器 smart-rtmpd: smart_rtmpd 是一款 rtmp、rtsp 服务器&#xff0c;非常好用&#xff0c;解压既运行&#xff0c;支持跨平台&#xff0c;无任何依赖&#xff0c;性能和 SRS 相比不分上下 2.推拉流 下载windows版本ffmpeg,并设置环境变量. 推流 ffmpeg -re -st…

170基于matlab的DNCNN图像降噪

基于matlab的DNCNN图像降噪&#xff0c;网络分为三部分&#xff0c;第一部分为ConvRelu&#xff08;一层&#xff09;&#xff0c;第二部分为ConvBNRelu&#xff08;若干层&#xff09;&#xff0c;第三部分为Conv&#xff08;一层&#xff09;&#xff0c;网络层数为17或者20层…

制造业客户数据安全解决方案(终端安全/文件加密/介质管理等)

针对前文制造业客户数据安全解决方案&#xff08;数据防泄密需求分析&#xff09;提到的泄密风险&#xff0c;本文详细介绍一套完整、合理的解决方案&#xff0c;通过该方案构建公司数据安全防护边界&#xff0c;自动加密、全方位保护数据安全。 PC端&#xff1a;https://isite…

VUE2整合markdown编辑器 mavon-editor

GITEE文档 文档中详细介绍了自定义工具栏等 toolbars: {bold: true, // 粗体italic: true, // 斜体header: true, // 标题underline: true, // 下划线strikethrough: true, // 中划线mark: true, // 标记superscript: true, // 上角标subscript: true, // 下角标quote: true, …

大气颗粒物与VOCs PMF源解析实践技术应用

目前&#xff0c;大气颗粒物和臭氧污染成为我国亟待解决的环境问题。颗粒物和臭氧污染不仅对气候和环境有重要影响&#xff0c;而且对人体健康有严重损害。而臭氧的前体物之一为挥发性有机物&#xff08;VOCs&#xff09;。为了高效、精准地治理区域大气颗粒物和臭氧污染&#…

[corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape

前言 题目来源&#xff1a;竞赛官网 – 建议这里下载&#xff0c;文件系统/带符号的 vmlinux 给了 参考 [corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape Exploiting poll_list Objects In The Linux Kernel – 原作者文章&#xff0c;poll_list 利用方式…

Linux 权限详解

目录 一、权限的概念 二、权限管理 三、文件访问权限的相关设置方法 3.1chmod 3.2chmod ax /home/abc.txt 一、权限的概念 Linux 下有两种用户&#xff1a;超级用户&#xff08; root &#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff…

深信服技术认证“SCCA-C”划重点:深信服超融合HCI

为帮助大家更加系统化地学习云计算知识&#xff0c;高效通过云计算工程师认证&#xff0c;深信服特推出“SCCA-C认证备考秘笈”&#xff0c;共十期内容。“考试重点”内容框架&#xff0c;帮助大家快速get重点知识 划重点来啦 *点击图片放大展示 深信服云计算认证&#xff08;S…

Shell 脚本系列 | shell三剑客

目录 1、三剑客介绍2、三剑客之—grep1. 常用参数2. 常用示例1.过滤以#开头的行和空白行2.找出所有的mp3文件包含艺术家jayZ&#xff0c;不包含remix3.计算匹配项的数目4.在匹配字符串周围打印出行5.匹配显示所有IP 3、三剑客之一sed1.常用参数2.常用示例1. 奇数行后增加2. 删除…

http协议工具:apache详解

目录 一、常见的http服务程序 1、 Apache HTTP Server 介绍 1.1 apache 概念 1.2 apache 功能 1.3 apache 特性 2、MPM&#xff08;multi-processing module&#xff09;工作模式 2.1 prefork 2.2 worker 2.3 event 二、Apache HTTP Server安装和相关文件 1、安装方…

MySQL|MySQL基础(求知讲堂-学习笔记【详】)

MySQL基础 目录 MySQL基础一、 MySQL的结构二、 管理数据库1&#xff09;查询所有的数据库2&#xff09;创建数据库3&#xff09;修改数据库的字符编码4&#xff09;删除数据库5&#xff09;切换操作的数据库 三、表的概念四、字段的数据类型4.1 整型4.2 浮点型(float和double)…

Linux密码重置不求人:三种方法教你轻松搞定普通用户密码更改

在Linux服务器管理中&#xff0c;为普通用户设置和管理密码是一项基础且重要的任务。通过为普通用户设置登录密码&#xff0c;可以确保系统的安全性和用户访问的合法性。本文将详细介绍在Linux系统中&#xff0c;如何通过三种不同的方法为普通用户设置登录密码。 Linux下&…

基于 java springboot+layui仓库管理系统

基于 java springbootlayui仓库管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源…

c++类和对象新手保姆级上手教学(下)

目录 前言&#xff1a; 初始化列表&#xff1a; explicit关键字&#xff1a; static成员&#xff1a; 友元函数&#xff1a; 友元类&#xff1a; 内部类&#xff1a; 匿名对象&#xff1a; 前言&#xff1a; 类和对象下篇中剩余的部分较为简单易理解&#xff0c;认真记住…

PC端封装侧边导航

PC端封装侧边导航 template <div v-if"showBox false" class"leftShow" click.stop"toggleBox"></div><div class"container" :class"{ show: showBox, fixed: fixedBox }"><div class"arrow&qu…

《C++ Primer Plus》《7、函数——C++的编程模块》

文章目录 前言1复习函数的基本知识1.1定义函数1.2函数原型和调用函数 2函数的参数和按值传递2.1多个参数2.2另一个接受两个参数的函数 3函数和数组3.1函数如何用指针来处理数组3.2将数组作为参数意味着什么3.3更多的数组函数示例3.4使用数组区间的函数3.5指针和const 4函数和二…

【实时渲染】图形处理单元

介绍 早期的图像加速技术是使用三角形扫描&#xff0c;将这些扫描的颜色通过插值显示在屏幕上&#xff0c;而且也拥有访问数据的能力&#xff0c;将这些访问的数据通过插值显示在屏幕上 程序内部又加上了许多的可见性的像素检查&#xff0c;如深度测试等&#xff0c;由于这些过…

Java集合1——Collection

集合是一种容器&#xff0c;用来装数据&#xff0c;类似于数组&#xff0c;但是集合的大小可变&#xff0c;开发中也经常能用到&#xff0c;为了满足不同的业务场景需求&#xff0c;JAVA还提供了不同特点的集合。 集合体系结构&#xff1a;单列集合(Collection)每个元素只包含…

uni-app 经验分享,从入门到离职(四)——页面栈以及页面跳转的 API(开发经验总结)

文章目录 &#x1f4cb;前言⏬关于专栏 &#x1f3af;什么是页面栈&#x1f9e9;页面跳转方法&#x1f4cc; uni.navigateTo(OBJECT)&#x1f4cc; uni.redirectTo(OBJECT)&#x1f4cc; uni.reLaunch(OBJECT)&#x1f4cc; uni.switchTab(OBJECT)&#x1f4cc; uni.navigateBa…

前端基础自学整理|HTML + JavaScript + DOM事件

目录 一、HTML 1、Html标签 2、Html元素 3、基本的HTML标签 二、CSS 样式 层叠样式表 三、JavaScript 使用示例 四、HTML DOM 通过可编程的对象模型&#xff0c;javaScript可以&#xff1a; window document 1、查找HTML元素 2、操作HTML元素 获取元素的属性 四…