数据结构——二叉树的顺序存储(堆)

news2025/1/25 4:45:51

二叉树的顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。 

 




概念

如果有一个关键码的集合K = {k0 ,k1 ,k2 ,k3…,k(n-1) },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足Ki <= K(2*i+1)且Ki <= K(2*i+2)(Ki >= K(2*i+1)且Ki >= K(2*i+2)) i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。


性质

堆中某个节点的值总是不大于或不小于其父节点的值。

堆总是一棵完全二叉树。


堆的实现

(本篇文章均采用小堆)

由于在物理上是一个数组,我们可以借助顺序表的思想

typedef int hpDataType;

typedef struct heap
{
	hpDataType* a;
	int size;
	int capacity;
}HP;

初始化与销毁

void HeapInit(HP* hp)
{
	hp->a = NULL;
	hp->size = 0;
	hp->capacity = 0;
}

void HeapDestroy(HP* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = 0;
	hp->capacity = 0;
}

插入

由于堆的特性,头插改变堆的结构,所以我们选择尾插

void HeapPush(HP* hp, hpDataType x)
{
	if (hp->size == hp->capacity)
	{
		int newCapacity = (hp->capacity + 1) * 2 - 1;
		hpDataType* tmp = (hpDataType*)realloc(hp->a, sizeof(hpDataType)*newCapacity);
		if (tmp == NULL)
		{
			printf("realloc error\n");
			exit(-1);
		}
		hp->a = tmp;
		hp->capacity = newCapacity;
	}
	hp->a[hp->size] = x;
	hp->size++;
	Adjustup(hp->a, hp->size - 1);
}

 

例如这样的一个堆,当我们在进行尾插时,这个数组的结构已经不符合堆的性质,所以我们要讲新插入的数据调整到合适的位置,我们称之为上调。

例如我们在上面的堆中插入一个20

我们通过比较它与它的父节点来判断是否进行交换

当它小于它的父节点或者它变为根节点时,变为小堆,停止交换

void Swap(hpDataType* m, hpDataType* n)
{
	hpDataType mid = 0;
	mid = *m;
	*m = *n;
	*n = mid;
}
void Adjustup(hpDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child!=parent&&a[child] < a[parent])
	{
		Swap(&a[child], &a[(child - 1)/2]);
		child = (child - 1) / 2;
		parent = (child - 1) / 2;
	}
}

void HeapPush(HP* hp, hpDataType x)
{
	if (hp->size == hp->capacity)
	{
		int newCapacity = (hp->capacity + 1) * 2 - 1;
		hpDataType* tmp = (hpDataType*)realloc(hp->a, sizeof(hpDataType)*newCapacity);
		if (tmp == NULL)
		{
			printf("realloc error\n");
			exit(-1);
		}
		hp->a = tmp;
		hp->capacity = newCapacity;
	}
	hp->a[hp->size] = x;
	hp->size++;
	Adjustup(hp->a, hp->size - 1);
}

删除

同样,删除分为头删和尾删,但在二叉树中,尾删并没有什么作用,而头删可以帮助我们选出最小(最大的数据),所以我们只考虑头删。

但若按照常规思路进行头删,会破坏堆的结构。

所以我们并不能采用常规的思路

这里我们可以将根节点与最后一个节点进行交换后进行尾删,然后将根节点(原本的最后一个节点)向下调整

 

 可以看到,在我们进行向下调整的时候,我们比较的是父节点和左右孩子节点中较小的一个,这是因为若是比较较大的孩子节点,交换后被交换的孩子节点依然大于未被交换的孩子节点。无法构成小堆(大堆相反)

例如在第一次调整中,若是比较较大的孩子节点

交换后30依然会大于26 

void Adjustdown(hpDataType* a, int size , int parent)
{
	int leftChild = (parent + 1) * 2 - 1;
	int rightChild = (parent + 1) * 2;
	int min = 0;
	while (leftChild<size)
	{
		if (rightChild < size && a[rightChild] < a[leftChild])
			min = rightChild;
		else
			min = leftChild;
		if (a[min] < a[parent])
		{
			Swap(&a[parent], &a[min]);
			parent = min;
			leftChild = (parent + 1) * 2 - 1;
			rightChild = (parent + 1) * 2;
		}
		else
			break;
	}
}

void HeapPop(HP* hp)
{
	assert(hp);
	assert(hp->size);
	Swap(&(hp->a)[0], &(hp->a)[hp->size - 1]);
	hp->size--;
	Adjustdown(hp->a,hp->size, 0);
}

堆的相关问题

TopK问题

TopK问题,指的是在一个大小为n的数组中寻找最小(最大)的前K个的问题

在此之前,我们通常会运用排序来解决。

但若n较大,时间复杂度会很大。

同时,我们也可以构建大小为n的小堆(大堆)来解决。

但这同样存在问题,那就是若n较大,栈区无法存储。

因此我们可以构建一个大小为K的小堆(大堆),当存储前K个后,通过比较新数据与根节点数据,来判断是否进行插入,最后选择出最小(最大)的前K个数据。

void PrintTopK(int* a, int n, int k)
{
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < k; i++)
	{
		HeapPush(&hp, a[i]);
	}
	for (int i = k; i < n; i++)
	{
		if (a[i] > HeapTop(&hp))
		{
			(hp.a)[0] = a[i];
			Adjustdown(hp.a, k ,0);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", (hp.a)[i]);
	}
	printf("\n");
}

堆排序

堆排序,指的是给出一个大小为n的数组,通过堆进行排序。

通常我们可以想到构建一个小堆(大堆),将数组一个个插入进去。

这样做空间复杂度为O(n)。

那么我们如何使空间复杂度达到O(1)呢?

首先我们默认为降序。

由于堆在物理上就是一个数组,所以我们可以直接对数组进行操作,使其首先构建成小堆(大堆)。我们可以先将第一个数据当做堆,在将后面的数据一个个插入(向上调整)

 

for (int i = 1; i < n; i++)
{
	Adjustup(a, i);
}

另外,我们还有另外一种方法

我们可以向下调整。但向下调整的前提是左右子树都为小堆(大堆)。因此,我们需要从最后一个非叶子节点(叶子节点没有左右子树,不需要调整)开始向下调整。

在构建好小堆(大堆)后,我们便可以进行排序的操作。

由于是对原数组进行操作,我们无法做到将根节点赋值给数组后进行删除。但在头删中,我们是将根节点与最后一个节点交换后向下调整,由于小堆的根节点最小,那么最小的数便被放置在数组的末尾,并且将其排除在堆外。

那么在第二次删除后,数组中倒数第二个数据便为15(第二小)。

由此可知,我们只需要不断进行删除操作,便能将小堆转变为降序

(注意:降序需要小堆,升序需要大堆,一定不要搞反)

如此,我们便可以完成堆排序

void HeapSort(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);
	}
}

构建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的 就是近似值,多几个节点不影响最终结果)。

 在堆排序中,我们讲述了构建堆的方法,我们可以统计各层节点的数量和移动层数来进行计算。

 

因此可以得出:构建堆的时间复杂度为O(N) 

 

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

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

相关文章

玩转MySQL:如何在高并发大流量情况下正确分库分表海量数据

引言 本篇数据库专栏内容&#xff0c;主要会讲解不同高并发场景下的MySQL架构设计方案&#xff0c;也包括对于各类大流量/大数据该如何优雅的处理&#xff0c;也包括架构调整后带来的后患又该如何解决&#xff1f;其中内容会涵盖库内分表、主从复制、读写分离、双主热备、垂直分…

极简而高效的沟通管理法(有点长,但都是干货)

我想说的意思不是说为了体现工作的价值&#xff0c;要故意提高工作的沟通成本&#xff0c;相反&#xff0c;我们的确需要通过归纳总结梳理标准话的流程、甚至是工具化的手段来降低一个领域的沟通&#xff0c;但我们同时工作在找更复杂、更需要去沟通的场景中&#xff0c;去创造…

【PTA-训练day16】L2-028 秀恩爱分得快 + L1-064 估值一亿的AI核心代码

&#xff01;L2-028 秀恩爱分得快 - 分块大模拟 PTA | 程序设计类实验辅助教学平台 这个题还是挺考验 函数合理运用 和 数据模拟处理能力 的 思路&#xff1a; 因为可能出现-0这种输入 所以不能是int型 stoi() 将字符串转化为整数先把每张照片的人 按照片编号储存因为题目只要…

手慢无!阿里云神作被《Spring Boot进阶原理实战》成功扒下,限时

小编又来给大家分享好书了&#xff1a;郑天民老师的 《Spring Boot进阶:原理、实战与面试题分析》&#xff0c;别问网上有没有开源版本&#xff01;问就是我也不知道&#xff0c;哈哈&#xff01;小编会在文末附电子版下载方式。 郑天民是谁&#xff1f; 资深架构师和技术专家…

非程序员,到底该不该学Python

前言 最近被各种Python的小广告轰炸。也有很多非程序员的朋友咨询Python相关的事儿。&#xff08;前两年是前端&#xff09; 所以今天不讲技术&#xff0c;纯BB。 进入正题吧&#xff1a; Python是啥&#xff1a; 编程语言。和大多数编程语言一样。它只能帮助人类完成一些…

3000字带你读懂:BI能解决报表解决不了的什么问题?

一、BI不等于报表 工作原因&#xff0c;老李经常跟不同行业的人打交道。不聊不知道&#xff0c;在大肆谈论“数字化转型”、“信创”、“业务对象数字化”、“BI”这类大而广的词语之下&#xff0c;隐藏着的却是国人的无知。搞业务的朋友不太懂这类工具和概念&#xff0c;我也…

mac怎么删除硬盘里面的东西?为什么苹果电脑无法删除移动硬盘文件?

mac怎么删除硬盘里面的东西&#xff1f;由于移动硬盘的文件系统是NTFS格式的&#xff0c;而这种格式与Mac电脑是不兼容的&#xff0c;Mac电脑没有权限对移动硬盘上的数据进行操作&#xff0c;Mac上不能把移动硬盘的数据删除了&#xff0c;那么&#xff0c;有没有什么操作方法&a…

状态模式(State)

参考&#xff1a; [状态设计模式 (refactoringguru.cn)](https://refactoringguru.cn/design-patterns/mediator) 4. 状态模式 — Graphic Design Patterns (design-patterns.readthedocs.io) design-patterns-cpp/State.cpp at master JakubVojvoda/design-patterns-cpp …

31-Vue之ECharts-饼图

ECharts-饼图前言饼图的特点饼图的基本实现饼图的常见效果显示数值南丁格尔图选中效果圆环前言 本篇来学习饼图的实现 饼图的特点 饼图可以很好地帮助用户快速了解不同分类的数据的占比情况 饼图的基本实现 ECharts 最基本的代码结构准备数据准备配置项 在 series 下设置 …

去年今日我凭借这份文档,摇身一变成了被BAT看中的幸运儿

我足够努力&#xff0c;当然也足够幸运。现在把这份文档和这份幸运分享给你们。 JVM 线程 JVM内存区域 JVM运行时内存 垃圾回收与算法 JAVA 四种引用类型 GC分代收集算法 VS 分区收集算法 GC垃圾收集器 JAVA IO/NIO JVM 类加载机制 由于篇幅限制小编&#xff0c;细节内…

使用Tensorflow2和Pytorch实现线性回归

使用Tensorflow2和Pytorch实现线性回归步骤Tensorflow2代码效果Pytorch代码效果步骤 准备步骤&#xff1a; 1. 创建数据集 2. 设置超参数 3. 创建模型(函数) 4. 选择损失函数 5. 选择优化器 训练步骤&#xff1a; 6. 通过模型(函数)前向传播 7. 计算损失 8. 对超参数求梯度 9…

【人脸识别】人脸实时检测与跟踪【含GUI Matlab源码 673期】

⛄一、简介 如何在视频流中检测到人脸以及人脸追踪。对象检测和跟踪在许多计算机视觉应用中都很重要&#xff0c;包括活动识别&#xff0c;汽车安全和监视。所以这篇主要总结MATLAB的人脸检测和跟踪。 首先看一下流程。检测人脸——>面部特征提取——>脸部追踪。 ⛄二、…

springcloud3 EurekaClient集群的搭建2

一 概述 1.1 概述 本文主要是搭建集成eurekaserver的几个客户端&#xff0c;即服务提供者&#xff0c;消费者。架构图如下所示 1.2 使用eureka整合的优点 使用Eureka管理注册的好处&#xff1a;消费者直接调用服务名称而不用在关系地址和端口&#xff0c;且该服务还有负载均…

[附源码]Nodejs计算机毕业设计基于的仓库管理系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

DBCO-PEG-Mesylate,Mesylate-PEG-DBCO,甲磺酸酯聚乙二醇环辛炔

一、试剂基团反应特点&#xff08;Reagent group reaction characteristics&#xff09;&#xff1a; DBCO-PEG-Mesylate属于高分子PEG&#xff0c;甲磺酸酯是甲磺酸与醇酯化而成的酯类化合物。“点击化学"一般由叠氮化物&#xff08;azide&#xff09;和炔烃&#xff08;…

React - 组件样式模块化

React - 组件样式模块化一. 存在的问题二. 解决样式冲突&#xff0c;组件样式模块化当多个组件使用相同类名时&#xff0c;设置的css样式会存在冲突渲染。 一. 存在的问题 例如有Page1、Page2两个组件&#xff0c;在 Page1 组件引入了css样式&#xff0c;Page2 组件未引入。 组…

用Excel写个摸球模拟器玩玩

用Excel写个摸球模拟器玩玩背景代码实现相关资料背景 最近对象有个需求&#xff0c;想要帮忙写个程序&#xff0c;实现功能&#xff1a;模拟两种颜色的球&#xff0c;随机摸球N次后&#xff0c;摸到不同颜色的次数。 考虑到非程序员的环境配置问题&#xff0c;直接用Excel中的…

【配电网规划】SOCPR和基于线性离散最优潮流(OPF)模型的配电网规划( DNP )(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

深兰科技接连斩获工业设计奖!出众产品设计,助AI产品一路领先

十余年来&#xff0c;第三代AI浪潮奔腾汹涌&#xff0c;中国AI产业从全面追赶到部分实现超越。两年前&#xff0c;AI更是正式成为国家七大新基建之一。从国家战略到基础设施&#xff0c;AI正全面地从文件走向现实&#xff0c;国内人工智能的市场规模也迅速扩大。这背后&#xf…

简易聊天室代码分享 js+socket.io

先言 这我以前写的&#xff0c;这里就是单纯分享下代码&#xff0c;不算正经文章。效果如下&#xff0c;前端用一个单html文件。然后后端用node.js和socket.io&#xff0c;也是只用一个单js文件就好。这里可以看下代码的实现逻辑就好&#xff0c;因为来连数据库才能运行的。有…