数据结构(四)————二叉树和堆(中)

制作不易,三连支持一下呗!!!

文章目录

  • 前言
  • 一、堆的概念及结构
  • 二、堆的实现
  • 三.堆的应用
  • 总结


前言

CSDN 

这篇博客介绍了二叉树中的基本概念和存储结构,接下来我们将运用这些结构来实现二叉树


一、堆的概念及结构

1.概念:

堆是一种完全二叉树,但是堆中每个节点都不大于(或不小于)其父节点,这样的完全二叉树就称为堆。

堆的性质:堆中每个节点都值都不大于(不小于)其父节点的值

                  堆是一种完全二叉树

堆分为大堆和小堆:根节点为最大值的是大堆,根节点为最小值的是小堆。

2.结构:

堆通常采用的是顺序存储的方式,即将数据存储在数组中,通过父节点和孩子节点下标的关系来相连起来。

typedef int HPDateType;

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

二.堆的实现

1.堆的初始化和销毁

这个部分比较简单,直接放代码

//初始化
void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

//销毁
void HPDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

2.堆的插入数据(向上调整算法) 

每次插入数据后我们都需要调整数据的位置,以保证满足堆的定义,这里我们写了一个向上调整函数

//向上调整算法
void AdjustUp(HPDateType* a,int 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;
		}
	}
}

这里我们建的是小堆,所以判断条件是孩子的值小于父亲的值时就交换。

所以 push数据时,在插入到size位置的基础上,只需要加一个向上调整函数的调用即可。

void HPPush(HP* php, HPDateType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacitty = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDateType* tmp = (HPDateType*)realloc(php->a, sizeof(HPDateType)*newcapacitty);
		if (tmp == NULL)
		{
			perror("realloc:");
			return;
		}
		php->capacity = newcapacitty;
		php->a = tmp;
	}

	php->a[php->size] = x;
	php->size++;

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

下面我们分析一下向上调整算法建堆的时间复杂度: 

	//建堆
    int i = 0;
	for(i = 0; i <10; i++)
	{
		HPPush(&hp, a[i]);
	}

假设我们要N个节点 ,树的深度是h。

这样我们就可以得到 2^{h-1}<N<=2^{h}-1

反解得\log (N+1)<h<(\log N)+1,近似可得h约等于logN。

因为向上调整最坏情况下会调整高度次,而高度约等于logN,所以向上调整算法的时间复杂度就是logN。

void HPInitArray(HP* php, HPDateType* a, int n)
{
	assert(php);
	php->a = (HPDateType*)malloc(sizeof(HPDateType)*n);
	if (php->a == NULL)
	{
		perror("Init:");
		return;
	}

	memcpy(php->a, a, n * sizeof(HPDateType));
	php->capacity = php->size = n;
	//建堆
	for (int i=1; i < php->size; i++)
	{
		AdjustUp(php->a, i);
	}
}

那么用向上调整算法建堆时,在插入第k层的数据时,最多向上调整k-1次,第k层有2^{k-1}个节点,这些节点共需向上调整(k-1)*2^{k-1}次。

所以时间复杂度o(N)=1*2^{1}+…+(h-1)*2^{h-1}=2^{h}*(h-2)+2

又因为h约等于logN

o(N)=N*(\logN-2)+2。近似为N*logN

3. 删除数据(向下调整算法)

注意:堆删除数据时是删除堆顶的数据,而不是最后一个位置的数据!!!

可能大家首先想到的删除方法是挪动数据向前一个覆盖。

但是这种方法有两个缺陷:

1.这种操作的时间复杂度是o(N),效率不是很高。

2.这种操作完全破坏了之前建立的堆的结构,最后我们还要耗费大量的操作时间来重新建堆。

因此,这里我们采用另一种思路:

先交换最后一个位置和堆顶元素,再size--。这样我们就删除了堆顶数据,并且并没有完全破坏掉堆的结构,因为除堆顶数据外,堆顶元素的左子树和右子树依旧还是堆结构,我们只需要将堆顶元素向下调整,将它放置在合理位置就可以了。

向下调整算法:

//向下调整算法
void AdjustDown(HPDateType* 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 = child * 2 + 1;
		}
		else {
			break;
		}
	}
}

 注意:我们依旧以小堆为例,在调整时我们的思路是:和孩子中小的那个值比较,如果孩子比父亲的值要小,就交换,并更新孩子和父亲的值,循环操作,直到终端结点。

向下调整算法的适用条件:下面的节点是堆。

那么我们pop操作就比较简单了。

pop函数:

//删除堆顶数据
void HPPop(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 HPInitArray(HP* php, HPDateType* a, int n)
{
	assert(php);
	php->a = (HPDateType*)malloc(sizeof(HPDateType)*n);
	if (php->a == NULL)
	{
		perror("Init:");
		return;
	}

	memcpy(php->a, a, n * sizeof(HPDateType));
	php->capacity = php->size = n;

	//向下调整建堆
	for (int i = (php->size - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}
}

同向上调整算法类似,时间复杂度O(N)=1*2^{h-2} +…+(h-1)*2^{0}=2^{h}-1-h.

由满二叉树h=log(N+1)得O(N)=N-log(N+1)

所以使用向下调整算法建堆的时间复杂度为O(N)=N。

比使用向上调整建堆效率提高了非常多!!!

4.其他一些小函数 

//取堆顶数据
HPDateType HeapTop(HP* php)
{
	assert(php);
	return php->a[0];
}
//判断堆是否为空
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

 这两个函数非常简单,有之前顺序表,链表,栈和队列的基础,应该是不难理解的。

三.堆的应用 (堆排序和TopK问题)

1.TopK问题

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

比如我们要从100亿个数据中找到最大的前十个数是多少。

我们没有了解堆之前可能想法就是:将数据存到一个数组中,用排序算法排序一下,最后取最大的十个数。

但是这样的方法实践中是不可行而且就算可行效率也不高的。

但是根据堆的性质,堆顶的元素就是最大值或最小值。那如果我们要找最大的十个数,我们就建小堆,初始先将所以数据中的前十个元素建成一个小堆,依次遍历数据,如果数据比堆顶元素大就将堆顶元素替换成这个数据,并向下调整,保持小堆的状态。直至结束,最后在小堆中的十个值就是最大的十个数。

时间复杂度O(N)=K+(N-K)*logK。

这里我们采用循环产生随机数的方法来创造大量随机数据来模拟TopK问题。

这样我们就创造了有10000个数据的文件,并且这些数据的大小都在1000000以内。

这时我们手动在10个数据后面补位使它们大小超过1000000,那么如果程序正确,我们最后打印出来的值是10个比1000000大的值。

结果和预期相同,证明我们都程序大概率是没什么问题的。 

 

2.堆排序

如果我们想要将一组数排成升序,我们就建大堆,要排成降序,就排成小堆

void HeapSort(int* a, int n)
{
	//建堆
	for (int i = (n - 1 - n) / 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--;
	}
}

堆排序的时间复杂度计算时和向上调整建堆一样,都是N*logN,我们忽略最开始建堆(O(N))的消耗。 


总结

这篇博客详细介绍了堆结构的实现和实践中的应用,希望对大家有所收获。

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

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

相关文章

一篇文章,系统性聊聊Java注解

你好&#xff01; 这类系统性聊聊***知识点的文章&#xff0c;是希望给大家带来对某个技术的全貌认识&#xff0c;如果大家喜欢&#xff0c;后续可以陆续更新此系列 下面&#xff0c;开始今天的分享 在之前&#xff0c;我们已经分享过注解相关的三个面试题&#xff0c; 今天的…

信号量、PV操作及软考高级试题解析

信号量 在并发系统中&#xff0c;信号量是用于控制公共资源访问权限的变量。信号量用于解决临界区问题&#xff0c;使得多任务环境下&#xff0c;进程能同步运行。此概念是由荷兰计算机科学家Dijkstra在1962年左右提出的。信号量仅仅跟踪还剩多少资源可用&#xff0c;不会跟踪…

Cloudera简介和安装部署

ChatGPT Cloudera 是一个基于 Apache Hadoop 的数据管理和分析平台。它是由 Hadoop 的几位创始人及早期贡献者于 2008 年创立的公司&#xff0c;并随着公司的不断发展&#xff0c;Cloudera 开始提供企业级的解决方案&#xff0c;帮助企业更好地利用 Hadoop 生态系统进行大数据…

2024.05.10作业

TCP服务器 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QList> #include <QMessageBox> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; …

mp4压缩怎么压缩?知道压缩原理和工具就会了!

在数字化时代&#xff0c;视频已成为我们生活中不可或缺的一部分。然而&#xff0c;随着视频质量的提升&#xff0c;文件大小也随之增加&#xff0c;给存储和传输带来了不小的挑战。因此&#xff0c;掌握MP4视频压缩技巧变得尤为重要。本文将为你详细介绍MP4压缩的多种方法&…

dev c++调试录入数字后回车直接关闭

1、我的dev c版本是5.11 2、输入7后&#xff0c;回车就没有了&#xff0c;原因是1013,1.cpp未包含在项目中 3、新建项目&#xff0c;并将test_debug.cpp包含在项目内&#xff0c;就可以下断点调试了

G.AB路线【蓝桥杯】/bfs+可重复走

AB路线 bfs可重复走 思路&#xff1a;本题和传统的bfs题目不同&#xff0c;本题为了满足题目先走K个A再走K个B&#xff0c;可能需要重复走某个格子才能继续走下去&#xff0c;故vis数组可以多开一维&#xff0c;vis[x][y][z]表示第z次走到x行y列这种情况是否出现过 A A A B B …

汇编语言——输入两个字数据(16位的数)X,Y,计算Z=X+Y,并把Z的结果显示出来

文章目录 以2进制输入&#xff0c;2进制输出&#xff08;无符号&#xff09;以2进制输入&#xff0c;2进制输出&#xff08;带符号&#xff09;以8进制输入&#xff0c;8进制输出以10进制输入&#xff0c;10进制输出以16进制输入&#xff0c;16进制输出 仅供参考 X、Y的输入可…

IATF16949认证是什么?

IATF16949认证是一项国际质量管理体系的认证标准&#xff0c;由国际汽车行业联合会&#xff08;IATF&#xff09;开发&#xff0c;旨在提高汽车行业的质量管理水平&#xff0c;满足客户对汽车部件和零部件的要求。该标准是在ISO/TS 16949标准的基础上&#xff0c;专门为汽车行业…

解决参考文献自动生成标号,换行时自动缩进

问题如下图所示&#xff0c;红色方框部分应该填充内容&#xff0c;但自动生成标号时不会填充&#xff1a; 解决方案&#xff1a; 1. 选中内容&#xff1a; 2. 找到布局-段落&#xff1a; 3. 选择“无”&#xff0c;即可。

【Linux操作系统】:文件操作

目录 前言 一、C语言中文件IO操作 1.文件的打开方式 2.fopen&#xff1a;打开文件 3.fread&#xff1a;读文件 4.fwrite:写文件 二、系统文件I/O 1.系统调用open、read、write 2.文件描述符fd 3.文件描述符的分配规则 4.重定向 5.缓冲区 6.理解文件系统 磁盘 磁盘…

富士Apeos 2350 NDA复印机报062 360代码故障

故障描述&#xff1a; 富士Apeos 2350 NDA复印机新机器刚拆箱安装&#xff0c;开机正常&#xff0c;自检扫描头一卡一卡的往前动几下就不动了、扫描灯也不亮扫描头也不能正常复位&#xff1b;按机器的复印键直接报062 360代码&#xff1b; 解答&#xff1a; 此代码为扫描故障&a…

多任务学习的优化算法:实现多个任务的最佳收敛

多任务学习的优化算法 多任务学习的优化算法&#xff1a;实现多个任务的最佳收敛多任务学习的挑战多任务学习的优化算法1. **梯度归一化&#xff08;Gradient Normalization, GradNorm&#xff09;**2. **多任务平衡&#xff08;Multi-Task Balancing, MTB&#xff09;**3. **弹…

Navicat工具连接人大金仓数据库

在使用人大金仓数据库时&#xff0c;可以选择使用人大金仓自带的连接工具&#xff0c;比如KingbaseES V8&#xff08;数据库开发管理工具&#xff09;工具&#xff0c;类似于navicat工具&#xff0c;两个工具都有优缺点&#xff0c;看个人喜好了。 但是在实际过程中&#xff0c…

pdffactory pro8.0虚拟打印机(附注册码)

PdfFactory pro是一款非常受欢迎的PDF虚拟打印机&#xff0c;可以帮助用户将你的其他文档保存为PDF格式。请为用户提供打印/发送/加密等多种实用功能&#xff0c;以及一套完善的PDF打印方案。 使用说明 下载pdfFactory Pro压缩包&#xff0c;解压后&#xff0c;双击exe文件&am…

【go项目01_学习记录10】

操作数据库 1 插入数据2 显示文章2.1 修改 articlesShowHandler() 函数2.2 代码解析 3 编辑文章3.1 添加路由3.2 编辑articlesEditHandler()3.3 新建 edit 模板3.4 代码重构3.5 完善articlesUpdateHandler()3.6 测试更新3.7 封装表单验证 1 插入数据 . . . func articlesStore…

Spark Streaming笔记总结(保姆级)

万字长文警告&#xff01;&#xff01;&#xff01; 目录 一、离线计算与流式计算 1.1 离线计算 1.1.1 离线计算的特点 1.1.2 离线计算的应用场景 1.1.3 离线计算代表技术 1.2 流式计算 1.2.1 流式计算的特点 1.2.2 流式计算的应用场景 1.2.3 流式计算的代表技术 二…

(十)JSP教程——config对象

config对象是脚本程序配置对象&#xff0c;表示当前JSP页面的配置信息。由于JSP页面通常无需配置&#xff0c;因此该对象在JSP页面中比较少见。 config对象可以读取一些初始化参数的值&#xff0c;而这些参数一般在web.xml配置文件中可以看到&#xff0c;并通过config对象的相应…

day5 qt

服务器头文件#ifndef MYWIDGET_H #define MYWIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QList> #include <QMessageBox> #include <QDebug> QT_BEGIN_NAMESPACE namespace Ui { class Mywidget; …

06.线程同步

互斥锁&#xff08;互斥量&#xff09; 描述 一个进程下的线程是共享资源的&#xff0c;通信便利的同时也造成了许多麻烦&#xff0c;线程程和线程之间如果同时访问一块资源就会出错&#xff0c;所以要引入一个互斥变量给它加锁&#xff0c;让它去协同不同线程程之间的访问&am…