【数据结构】堆的详解

文章目录

  • 堆的简介
  • 堆的实现
    • 堆的插入数据
    • 堆的删除数据
  • 堆排序
    • 向上调整和向下调整的时间复杂度的分析
  • 大量数据的topk问题

堆的简介

今天要写的数据结构是,什么是堆呢?堆其实是一种完全二叉树,只不过它是有条件的。
堆分为两种,一种是大根堆,又叫大堆,顾名思义就是每棵子树的父亲节点都大于孩子节点,另一种是小根堆,又叫小堆,自然就是每颗子树的父亲节点都大于孩子节点。
堆一般在内存中是以数组的形式存储的,就是从根节点开始从上往下,从左往右依次存储,下面是一个大根堆的逻辑形式和物理中的存储形式。
在这里插入图片描述
这个堆就满足我们上面说的条件,标红的是数组的下标。

不知道你有没有发现,每个父亲节点和它的孩子节点在下标上有一定的关系,左孩子=父亲*2+1右孩子=父亲*2+2,你可以用图上的结点来试一下。
那么怎么通过孩子来找父亲呢?其实 孩子-1/2就是它的父亲节点,最简单的下标0 1 2来试一下,1和2减去1再除2就是0。

你可能会说这有什么作用呢?它结构相对与之前也变复杂了,那么自然,它解决问题的效率也就会更高,比如说我们冒泡排序的时间复杂度是O(N^2),但是我们后面要讲的堆排序的时间复杂度就是O(N*logN)。以及后面的topk问题都需要用到我们的堆,因为它处理起来确实是很方便的。

堆的实现

下面我们来实现一下这种数据结构

我们说过,堆是以数组的形式在内存中存储的,所以它的类型就类似于顺序表。

typedef int HPDataType;typedef struct Heap {HPDataType* a;int size;int capacity;
}HP;

初始化和销毁函数就非常的简单了,关键就是在于它里面的操作和顺序表是不一样的,下面我们来着重说一下它里面的操作是什么样的,怎么把一个无序的数组变成一个有一定顺序的堆。

堆的插入数据

下面就是关键的插入函数,我们可以一个元素一个元素的插入,当然也可以一下插入一整个数组,它们的核心关键不会变化,那就是调整这一步。什么意思呢?就是我们插入的话肯定是在数组尾部插入,之后再调整整个数组,让这个数组变成堆。那么问题来了,怎么调整成了关键。

以小根堆为例,当我们插入第一个数时,堆中就只有一个数据,那么它自然而然就是一个小根堆,从插入第二个数时,我们就要开始调整了。从后面插入东西,把它调到前面,我们叫做向上调整。

比如说我们要给20这个节点插入一个左孩子1
在这里插入图片描述
在插入1之前我们这是一个小根堆,那么我们现在要做的就是调整这个1的位置,让它重新形成一个小根堆。其实这个数值之间的大小关系只存在与父亲与孩子之间,兄弟之间并不存在这种关系,所以我们只需要调整父亲与孩子就可以了,孩子比父亲小就交换它们之间的位置,直到满足一个小根堆的条件为止
在这里插入图片描述
在这里插入图片描述
它的调整的一个基本思想就是这样

下面是向上调整的一个函数

void AdjustUp(HPDataType* a, int child) {//child是要向上调整的数据的下标int parent = (child - 1) / 2;while (child > 0) {if (a[child] < a[parent]) {Swap(&a[child], &a[parent]);child = parent;parent = (parent - 1) / 2;}else {break;}}
}

有了这个函数,我们就可以实现堆的逐个数据插入了

void HeapPush(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");exit(-1);}php->a = tmp;php->capacity = newCapacity;}php->a[php->size] = x;//数组末尾插入新数据php->size++;AdjustUp(php->a, php->size - 1);//将插入的数据向上调整
}

我们要实现整个数组去插入到一个堆中其实也一样,就先全部插入然后从第二个元素开始向上调整,直到全部调整完。

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);}
}

堆的删除数据

这里的删除呢其实不是正常意义上的删除,而是取出一个有意义的数据,这里以小根堆为例,它最有意义的数据是什么呢?当然就是它的根节点,因为它是这个队中最小的数据。比如说,topk问题,就是在一堆数据中去找到k个最大或最小的数据。
接下来我们说一下如何去取出根节点且让它还能保持成一个堆。就是让根节点和最后一个数据交换位置,在删掉最后一个位置,最后让根节点的数据向下调整,和向下调整在逻辑上向上调整是相反的,但实现方式都是差不多的,但是有一个区别,这里以小根堆为例,就是每一个父亲节点可能有两个孩子结点,要处理的父亲节点要跟小的结点交换,直到最后满足一个堆为止。

下面是向下调整函数

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;child = parent * 2 + 1;}else {break;}}
}

那么我们的删除函数也就很简单了

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);
}

堆排序

我们之所以创建堆,是因为它在某些方面确实是有一些优势的,下面是利用堆去实现排序功能

我们对一个数组去排序,可以先把这个数组调整成一个数据间关系与堆相同的样子,之后再往外取出数据,因为取出的话取出的就是极值
并且,排升序要建大根堆,排降序要建小根堆
为什么呢?我们以排升序为例,建大根堆,因为根节点肯定是最大的元素,把根节点和最后一个节点交换位置,就排好最大的这个元素了,之后同理再排前几个元素就可以了。

下面用代码来实现一下

void my_heap_sort(int* a, int n)
{for (int i = 1; i < n; i++) {//类似于建造堆AdjustUp(a, i);}for (int i = n - 1; i > 0; i--) {Swap(&a[i], &a[0]);AdjustDown(a, i, 0);}
}
int main() {int a[] = { 45,12,7,9,13,62,96,17,100,46,23,85 };my_heap_sort(a, sizeof(a) / sizeof(a[0]));for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {printf("%d ", a[i]);}return 0;
}

在这里插入图片描述

它就很容易的完成了排序功能,它的时间复杂度就是O(N*logN),确实比之前学的冒泡排序要高效一些

但是有个问题,我们在写这个堆排序的时候还要写两个函数,确实比较麻烦,其实我们可以只用向下调整的。
还是那个原理,叶子节点单拿出来是没有什么关系的,所以向下调整的话,只需要倒着从最后一个父亲节点开始向下调整就可以了,直到根节点也调整完。
所以说,可以只用向下调整,就是把上面代码的建堆过程改成下面的就可以了

for (int i = (n - 1 - 1) / 2; i >= 0; i--) {AdjustDown(a, n, i);}

那么问题又来了,怎么找到最后一个父亲节点呢?其实最后一个叶子的父亲不就是最后一个父亲节点吗?所以这个代码中i的初始值是这样解释的,i-1是最后一个数据的下标,用这个下标减一除二就是它的父亲节点

向上调整和向下调整的时间复杂度的分析

我们来计算一下一个高度为h的完全二叉树从全乱到成为一个堆(每个结点都是最坏情况)需要调整多少步
先看小根堆
在这里插入图片描述
再看大根堆
在这里插入图片描述

可以发现它们不是一个数量级,看大根堆,2的h次方个数据大概要调整2的h次方次,所以时间复杂度为O(N),同理,小根堆的时间复杂度为O(N*logN)

大量数据的topk问题

有时候我们要处理的问题有很多,多到不能在内存中处理,我们必须得把数据放到文件中,那么这时我们应该如何处理topk问题呢?

首先我们创建一个函数,用来在文件中放入一百万个随机数据,当然这些数据都要小于一个值,我们设置为一百万,然后再去文件中随机更改五个数据,让这五个数据大于一百万,如果能成功找到这五个数据的话,那么我们的操作就成功了
有不会文件操作的可以去看我的另外一篇博客,链接如下:
链接:文件操作

void CreateNData() {int n = 1000000;srand((unsigned int)time(NULL));FILE* fin = fopen("data.txt", "w");if (fin == NULL) {perror("fopen error");return;}for (int i = 0; i < n; i++) {int x = (int)(((double)rand() / RAND_MAX) * 1000000);//这里产生的值小于一百万fprintf(fin,"%d\n", x);}fclose(fin);fin = NULL;
}

之后我们在这个data.txt的文件中就随机产生了一百万个数据,这里一百万个数据都小于一百万,因为RAND_MAX是32767,所以我们可以用上面的方法使产生的数据小于一百万
在这里插入图片描述
之后我们随机改k个数,让这k个数大于一百万,让程序去找

怎么找呢?我们比如说要找十个数,然后我们创建一个能存放十个数的堆,先把文件中前十个数据放入堆中并排序成一个小根堆,根节点那个值肯定是最小的,后面的数据中有大于根节点的就交换,并重新形成一个堆,最后留下的就是最大的十个数

void PrintTopK(const char* filename, int k) {FILE* fout = fopen(filename, "r");if (fout == NULL) {perror("fopen fail");return;}int* minheap = (int*)malloc(sizeof(int) * k);if (minheap == NULL) {perror("malloc fail");return;}for (int i = 0; i < k; i++) {fscanf(fout,"%d", &minheap[i]);}for (int i = (k - 2) / 2; i >= 0; i--) {AdjustDown(minheap, k, i);}int x = 0;while (fscanf(fout, "%d", &x) != EOF) {if (x > minheap[0]) {minheap[0] = x;AdjustDown(minheap, k, 0);}}for (int i = 0; i < k; i++) {printf("%d ", minheap[i]);}printf("\n");free(minheap);fclose(fout);
}

在这里插入图片描述
它也是成功找到了我埋藏的这十个最大的数了

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

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

相关文章

网站搬家的多种方法

网站搬家&#xff0c;把网站从一个服务器迁移到另一个服务器&#xff0c;涉及到网站文件和数据库的备份、上传、导入等操作&#xff0c;最重要的是备份网站&#xff0c;避免迁移出现问题无法恢复网站。 根据不同的情景和需求&#xff0c;网站搬家的方法有多种&#xff0c;下面…

Mysql,SqlServer,Oracle获取库名 表名 列名

先看下需求背景&#xff1a; 获取某个数据源连接下所有库名&#xff0c;库下所有表名&#xff0c;表中所有字段 1.MySql 先说MySql吧&#xff0c;最简单 1.1获得所有数据库库名 这是一个mysql和sqlserver公用的方法&#xff0c;这里url不用担心数据库问题&#xff0c;他其实…

[Ubuntu 18.04] 搭建文件夹共享之Samba服务器

Samba是一个开源项目,允许Windows用户在Linux和Unix系统上进行文件共享。 Samba服务器是一个可以让Linux或Unix系统在网络上充当Windows NT/2000/XP/2003等网络操作系统的共享资源的软件。它允许用户通过SMB/CIFS协议在Linux或Unix系统与Windows共享资源。 Samba服务器的主要…

Android Apk一键打包上传至蒲公英平台的gradle脚本

一、背景 项目中每次手动打包后&#xff0c;生成的测试包&#xff0c;都需要手动打开蒲公英平台的网址&#xff0c;登录账号&#xff0c;手动上传apk。之前写过一键上传至fir平台的脚本&#xff0c;想着这次可以搞一下一键打包上传至蒲公英的gradle脚本&#xff0c;提高下工作…

UE4 中可全局获取的变量(例如游戏实例、玩家控制器等) 详解

目录 0 引言1 全局对象&#xff08;全局变量&#xff09;1.1 游戏实例 GameInstance1.1.1 介绍1.1.2 使用 GameInstance 1.2 玩家控制器 PlayerController1.3 游戏世界类 UWorld &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;UE虚幻引擎专栏&…

[数据分析与可视化] 基于Python绘制简单动图

动画是一种高效的可视化工具&#xff0c;能够提升用户的吸引力和视觉体验&#xff0c;有助于以富有意义的方式呈现数据可视化。本文的主要介绍在Python中两种简单制作动图的方法。其中一种方法是使用matplotlib的Animations模块绘制动图&#xff0c;另一种方法是基于Pillow生成…

手搭手Ajax经典基础案例省市联动

环境介绍 技术栈 springbootmybatis-plusmysql 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http:/…

在pycharm中创建python模板文件

File——>Setting——>File and Code Templates——>Python Scripts 在文本框中输入模板内容

javaEE -7(网络原理初识 --- 7000字)

一&#xff1a;网络初识 计算机的独立模式是指多台计算机在网络中相互独立运行&#xff0c;彼此之间不共享资源或信息。在早期&#xff0c;计算机主要采用独立模式&#xff0c;每台计算机都拥有自己的操作系统、应用程序和数据&#xff0c;它们之间没有直接的连接或通信。 在…

【大数据】Hadoop

文章目录 概述Hadoop组成HDFSMapReduce写MapReduce程序&#xff08;Hadoop streaming&#xff09; YARNHadoop 启动 工作方式Hadoop的主从工作方式Hadoop的守护进程 运行模式本地运行模式伪分布式运行模式完全分布式运行模式 Hadoop高可用的解决方案ZooKeeper quorumZKFC 环境搭…

Elasticsearch聚合----aggregations的简单使用

文章目录 Getting started1、搜索 address 中包含 mill 的所有人的年龄分布以及平均年龄&#xff0c;但不显示这些人的详情2、size0不展示命中记录&#xff0c;只展示聚合结果3、按照年龄聚合&#xff0c;并且请求这些年龄段的这些人的平均薪资4、查出所有年龄分布&#xff0c;…

hadoop伪分布式安装部署

首先jdk安装完毕 jdk安装文档参考&#xff1a; Linux 环境下安装JDK1.8并配置环境变量_linux安装jdk1.8并配置环境变量_Xi-Yuan的博客-CSDN博客 准备好hadoop的安装包 我的下载地址如下&#xff1a; We Transfer Gratuit. Envoi scuris de gros fichiers. 将hadoop包上传到随…

C++前缀和算法的应用:装包裹的最小浪费空间 原理源码测试用例

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 题目 给你 n 个包裹&#xff0c;你需要把它们装在箱子里&#xff0c;每个箱子装一个包裹。总共有 m 个供应商提供 不同尺寸 的箱子&#xff08;每个规格都有无数个箱…

golang小游戏:飞翔的小鸟

游戏开发总体思路 首先要选取一个合适的图形化界面进行开发。该项目选取的是 ebiten 一个用于创建2D游戏和图形应用程序的游戏引擎&#xff0c;提供了一些简单的GUI功能。 其次明确游戏设计思路。飞翔的小鸟共分为三个场景。 第一个场景就是游戏开始前的准备阶段&#xff0c…

【Javascript】不满意网上的Token无感知刷新方案,自己琢磨了个感觉还不错~

​前言 大家设想一下&#xff0c;如果有一个超级大的表单页面&#xff0c;用户好不容易填完了&#xff0c;然后点击提交&#xff0c;这个时候请求接口居然返回401&#xff0c;然后跳转到登录页。。。那用户心里肯定是一万个草泥马~~~ 所以项目里实现token无感知刷新是很有必要…

vscode Coder Runner 运行C++

1. 设置Code Runner 2. 防止输入读不到&#xff0c;把在终端运行勾上。 3. 设置minw/bin的环境变量 安装mingw教程&#xff1a;https://blog.csdn.net/fancy_male/article/details/133992000 4. 见图

ubuntu18.04双系统安装(2023最新最详细)以及解决重启后发现进不了Ubuntu问题

目录 一.简介 二.安装教程 1.首先确定了电脑的引导格式是UEFIGPT还是BIOSMBR 2. 使用Windows磁盘管理划分足够的磁盘空间 3. 开始安装 三.重启后发现自动进入WIN10系统了&#xff0c;进不了Ubuntu&#xff1f; 一.简介 Linux是一种自由和开放源代码的操作系统内核&#x…

Deno 的配置文件、框架,标准库

目录 1、配置文件 imports 和scopes tasks lint fmt lock nodeModulesDir npmRegistry compilerOptions 一个全的示例 2、Web框架 2.1 Deno 原生框架 Fresh Aleph Ultra Lume Oak 3、标准库 3.1 版本和稳定性 1、配置文件 Deno支持一个配置文件&#xff0c…

MR混合现实情景实训教学系统在旅游管理专业中的应用

在旅游管理专业中&#xff0c;MR混合现实情景实训教学系统的主要应用包括但不限于以下几个方面&#xff1a; 1. 实地考察的替代&#xff1a;对于一些无法实地考察的景点或设施&#xff0c;学生可以通过MR系统进行虚拟参观&#xff0c;从而了解其实际情况。这不仅可以减少时间和…

【C++】STL容器——【深浅拷贝】与【写时拷贝】对比详解(拷贝构造)(10)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 目录 一.深浅拷贝浅拷贝&#xff1a;深拷贝&#xff1a; 二.写时拷贝 一.深浅拷贝 (默认拷贝构造运用 引用 防止死递归的后遗症&#…