【数据结构】堆的详解

文章目录

  • 堆的简介
  • 堆的实现
    • 堆的插入数据
    • 堆的删除数据
  • 堆排序
    • 向上调整和向下调整的时间复杂度的分析
  • 大量数据的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);
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【AGC】更新应用信息报未知错误解决方法

【问题描述】 最近有几个开发者遇到了一个问题&#xff0c;他们在AGC控制台配置好应用信息的图标和截图之后&#xff0c;点击保存按钮会弹出“未知错误&#xff0c;请稍后再试”的异常报错&#xff0c;导致无法正确保存应用配置信息。 出错页面如图所示。 ​​ 【解决方案】 …

Real3D FlipBook jQuery Plugin 3.41 Crack

Real3D FlipBook 和 PDF 查看器 jQuery 插件 - CodeCanyon 待售物品 实时预览 截图 视频预览 Real3D Flipbook jQuery 插件 - 1 Real3D Flipbook jQuery 插件 - 2 Real3D Flipbook jQuery 插件 - 3 新功能 – REAL3D FLIPBOOK JQUERY 插件的 PDF 到图像转换器 一款用于将…

3分钟教你用Python+Appium实现自动化测试

一、环境准备 1.脚本语言&#xff1a;Python3.x IDE&#xff1a;安装Pycharm 2.安装Java JDK 、Android SDK 3.adb环境&#xff0c;path添加E:\Software\Android_SDK\platform-tools 4.安装Appium for windows&#xff0c;官网地址 Redirecting 点击下载按钮会到GitHub…

软硬件架构分层总结

一、前言 软件系统很多架构图我们经常看到是这样的三段 就是这三段就可以演化出很多层 二、硬件架构分层 硬件层&#xff0c;基本是计算机硬件的体系结构&#xff0c;包括硬盘设备&#xff0c;cpu&#xff0c;内存&#xff0c;控制器&#xff0c;运算器&#xff0c;寄存器&am…

【会议征稿通知】2024第四届神经网络、信息与通信工程国际学术会议(NNICE 2024)

2024第四届神经网络、信息与通信工程国际学术会议&#xff08;NNICE 2024&#xff09; 2024 4th International Conference on Neural Networks, Information and Communication Engineering 2024第四神经网络、信息与通信工程国际学术会议&#xff08;NNICE 2024&#xff0…

Linux用户及文件权限管理

一、Linux 用户管理 Linux 是一个可以实现多用户登录的操作系统&#xff0c;比如“李雷”和“韩梅梅”都可以同时登录同一台主机&#xff0c;他们共享一些主机的资源&#xff0c;但他们也分别有自己的用户空间&#xff0c;用于存放各自的文件。但实际上他们的文件都是放在同一…

javascript: Sorting Algorithms

/** * file Sort.js * ide:vscode JavaScript Sorting Algorithms * 插件&#xff1a;IntelliSense,JSDoc,CodeLens,Debugger for Chrome, 静态代码检查&#xff1a;ESLint,JSHint,Flow Langugae Support,StandardJS-JavaScript Standard Style, koroFileHeader(文件头注释), …

某网站互动数据采集

1&#xff0c;网址 aHR0cHM6Ly9uZXdzLmZ1dHVubi5jb20vcG9zdC8zMzE4MzE1OQ2&#xff0c;找到返回互动数的请求包 3&#xff0c;采集互动数据加密信息如下 4&#xff0c;察看抓到的包&#xff0c;不难发现futu-offline-csrf-v2和futu-x-csrf-token-v2这两个参数在首页的请求中有…

基于斑马优化的BP神经网络(分类应用) - 附代码

基于斑马优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于斑马优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.斑马优化BP神经网络3.1 BP神经网络参数设置3.2 斑马算法应用 4.测试结果&#xff1a;5.M…

Python数据结构(树)

Python数据结构&#xff08;树&#xff09; 树的概念 树(英语: tree)是一种抽象数据类型ADT) 或是实作这种抽象数据类型的数据结构&#xff0c;用来模拟具有树状结构性质的数据集合。它是由n(n>1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一…

mysql 数据库 表结构生成word文档

1、背景 我们在做项目时&#xff0c;表设计文档都是非常重要的&#xff0c;可以让开发人员快速了解表与业务的关系、表之间的关系。 产品在不停迭代的过程中&#xff0c;表的结构也会有相应的变化&#xff0c;我们需要将变化更新的表设计文档中。以前我们是人工方式更新文档&…

reactNative导入excel文件

组件内导入 import {TouchableOpacity,PermissionsAndroid} from react-native; import RNFS from react-native-fs; import XLSX from xlsx; import DocumentPicker from react-native-document-picker; import {Buffer} from buffer;// 需要安装一下三个,Buffer和react-nati…

TP4057替代DP4057 500mA线性锂离子电池充电器芯片

描述 DP4057是一款完整的单节锂离子电池带电池正负极反接保护采用恒定电流/恒定电压线性充电器。其SOT封装与较少的外部元件数目使得DP4057成为便携式应用的理想选择。DP4057 可以适合USB电源和适配器电源工作。 由于采用了内部PMOSFET架构&#xff0c;加.上防倒充电路&#xf…

隧道代理 vs 普通代理:哪种更适合您的爬虫应用?

前言 随着互联网的普及&#xff0c;爬虫技术在多个领域得到广泛应用。在进行爬虫开发时&#xff0c;代理服务器是不可或缺的工具之一。代理服务器可以隐藏客户端的真实 IP 地址和位置&#xff0c;从而保护客户端的隐私&#xff0c;同时通过代理可以绕过一些网络限制和安全机制…

【JavaEE】网络编程---TCP数据报套接字编程

一、TCP数据报套接字编程 1.1 ServerSocket API ServerSocket 是创建TCP服务端Socket的API ServerSocket 构造方法&#xff1a; ServerSocket 方法&#xff1a; 1.2 Socket API Socket 是客户端Socket&#xff0c;或服务端中接收到客户端建立连接&#xff08;accept方法&…

好用的Visio绘图文件工具 VSD Viewer最新 for mac

VSD Viewer是一款可以查看Microsoft Visio绘图文件的工具&#xff0c;适用于Windows和macOS操作系统。它具有以下优点&#xff1a; 直观易用&#xff1a;VSD Viewer的用户界面非常简单直观&#xff0c;易于使用。支持多种文件格式&#xff1a;VSD Viewer支持多种Visio文件格式…

短视频矩阵系统搭建/源头----源码

一、智能剪辑、矩阵分发、无人直播、爆款文案于一体独立应用开发 抖去推----主要针对本地生活的----移动端(小程序软件系统&#xff0c;目前是全国源头独立开发)&#xff0c;开发功能大拆解分享&#xff0c;功能大拆解&#xff1a; 7大模型剪辑法&#xff08;数学阶乘&#xff…

HTML页面获取URL传递的参数值

如&#xff1a; // 查询url上链接的参数与参数值 function getQueryString(name) {var url window.location.search; // 获取URLvar pattern new RegExp("[\?\&]" name "([^\&])", "i"); // 正则匹配URLvar matcher pattern.exec(…

企业如何保护机密文件安全

企业如何保护机密文件安全&#xff0c;数据加密技术有哪些 随着公司业务的不断发展&#xff0c;公司机密文件的保护是一家公司不可忽视的问题。机密文件包含了企业的核心信息&#xff0c;如客户资料、产品方案、财务数据等。 安企神数据防泄密系统下载试用 企业数据一旦泄露…

HTTP响应

HTTP响应分为四个部分&#xff1a; 首行&#xff1a;HTTP/1.1&#xff08;首行&#xff09; 200&#xff08;状态码&#xff09; OK&#xff08;状态码描述&#xff09;header&#xff1a;空行&#xff1a;表示header的结束标记body&#xff1a;正文 HTTP状态码&#xff1a;…