【数据结构】二叉树顺序结构及实现

news2024/10/7 6:42:18

在这里插入图片描述

🚀write in front🚀
📜所属专栏:初阶数据结构
🛰️博客主页:睿睿的博客主页
🛰️代码仓库:🎉VS2022_C语言仓库
🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
关注我,关注我,关注我你们将会看到更多的优质内容!!

在这里插入图片描述

文章目录

  • 前言
  • 一. 二叉树的顺序结构
  • 二.堆的概念及结构
    • 小根堆:
    • 大根堆:
    • 注意:
  • 三.堆的实现:
    • 1.堆的插入(向上调整算法):
    • 2.堆的删除(向下调整算法):
    • 3.堆的构建:
  • 三.堆的应用:
    • 1.堆排序:
      • 步骤1:建堆:
        • 向上调整建堆:
        • 向下调整建堆:
      • 步骤2:堆删除思想:
    • 2.TOP-K问题
  • 总结

前言

  在前面的学习中,我们实现了栈与队列的实现。今天我们就通过顺序表来实现二叉树!
在这里插入图片描述

一. 二叉树的顺序结构

我们如何让顺序表和二叉树建立联系呢?

我们可以先对二叉树的每个结点进行编号,通过数组的连续排列建立以下联系:
在这里插入图片描述
这样我们就可以通过数组的下标来模拟实现二叉树了!
在这里插入图片描述
但是普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。
在这里插入图片描述
所以我们的数组存储表示二叉树只适合完全二叉树

二.堆的概念及结构

在这里插入图片描述
简单的来说,就是一颗完全二叉树,并且有以下性质:

  • 堆中某个节点的值总是不大于不小于父节点的值;
  • 堆总是一棵完全二叉树

其中分为小根堆和大根堆:

小根堆:

树中所有父亲都小于等于孩子:
在这里插入图片描述

大根堆:

树中所有父亲都大于等于孩子
在这里插入图片描述

注意:

在数据结构里面我们所学的栈,堆都是一种数据结构,他们与操作系统
中的栈和堆是两回事,一个是数据结构,一个是操作系统相关的知识,一定不要搞混淆!

三.堆的实现:

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

void HeapInit(HP* php);
void HeapDestroy(HP* php);

void HeapPush(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(HPDataType* a, int n, int parent);

1.堆的插入(向上调整算法):

  先插入一个数到数组的尾上,再进行向上调整算法直到满足堆

向上调整算法的条件是:
除了child结点,之前的结点都是堆
在这里插入图片描述
代码实现:

void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[child] > a[parent])
		{
			swap(&a[child],&a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->capacity == php->size)
	{
		HPDataType* ptr = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
		if (ptr == NULL)
		{
			perror("realloc::fail");
			return;
		}
		php->a = ptr;
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size-1);
}

2.堆的删除(向下调整算法):

  在这里删除堆是指删除堆顶的数据,因为删除堆尾元素没有什么实际的意义。将堆顶的数据与最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

向下调整算法的条件是:

左右子树都为堆!
在这里插入图片描述
代码实现:

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

3.堆的构建:

这里建议把后面的堆排序看了在来看这段代码:

void HeapInitArray(HP* php, int* a, int n)
{
	assert(php);

	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		return;
	}

	php->size = n;
	php->capacity = n;

	// 建堆
	for (int i = (n-2)/2; i >= 0; --i)
	{
		AdjustDown(php->a, php->size, i);
	}
}

这里的意思是给你一个属数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。

三.堆的应用:

1.堆排序:

  通过上面的学习我们发现,堆有排序的功能。但是如果我们要通过每次建堆的方式来排序,那还是挺浪费空间的。为什么不在原有的数组里面进行堆排序呢
堆排序即利用堆的思想来进行排序,总共分为两个步骤

  1. 建堆
    升序:建大堆
    降序:建小堆
  2. 利用堆删除思想来进行排序

  在这里会有同学问道,根据堆的性质,大堆不就是降序,小堆不就是升序吗?为什么升序要建大堆,降序要建小堆呢?

  其实啊,我们的堆在数组中的排序并不是完全的。对于升序,如果我们非要通过建小堆来排序,只能通过拿走堆顶,然后将剩下的元素进行重新建立大堆的方式来寻找第二小的元素,这样的时间效率会非常低,而且过程非常的麻烦。
在这里插入图片描述
那么我们应该怎么做呢?

步骤1:建堆:

向上调整建堆:

  向上调整建堆就相当于堆的插入,在原数组的空间里,每插入一个数,就向上调整一遍,代码如下:

void Heapsort(HPDataType* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		AdjustUp(a, i);
	}
}

当然我们可以来看看这种方法的时间复杂度:
在这里插入图片描述
通过求和:
在这里插入图片描述
通过错位相减法可以得到:
在这里插入图片描述
由此可见,向上调整建堆的时间复杂度为O(N*logN)

向下调整建堆:

  既然我们可以向上调整建堆,能不能向下调整建堆呢?答案是肯定的,只要我们找到最后一个叶子结点的父结点,把该结点及该结点以前的结点向下调整,这样就可以很好的建堆了。代码如下:

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

时间复杂度:
在这里插入图片描述
同样的我们通过求和可以得到以下结果:
在这里插入图片描述
通过错位相减得到:
在这里插入图片描述
所以向下调整算法的时间复杂度为O(N)。

步骤2:堆删除思想:

  以升序为例,在我们建完大堆之后,将大堆的第一个元素最后一个元素交换位置,让size- -,随后将堆进行向下调整(这里就是堆删除的思想)即可。在向下调整的过程中就会将第二大的元素调整到第一个元素,随后在将第一个元素和倒数第二个元素交换……以此循环即可实现排序:

void Heapsort(HPDataType* a, int n)
{
	//向上调整建堆
	for (int i = 0; i < n; i++)
	{
		AdjustUp(a, i);
	}
	//向下调整建堆
	for (int i = (n - 2) / 2; i > 0; i--)
	{
		AdjustDown(a, n,i);
	}
	//堆删除

	int end = n - 1;
	for (int i = end; i > 0; i--)
	{
		swap(&a[i], &a[0]);
		end--;
		AdjustDown(a, end, i);
	}

}

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

  其实上面的时间复杂度可以很明显的看出向下调整算法的O(N)更小,因为向上调整算法是更多的结点调整更多次向下调整算法是更少的结点调整更多次,所以向下调整算法的时间复杂度更小。

2.TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大排序就不太可取了(可能数据都不能全部加载到内存中)。

在这里插入图片描述
顺便来复习一下单位的换算:
在这里插入图片描述
最佳的方式就是用来解决,基本思路如下:

假设我们要从N个元素取最大/最小的K个元素

  1. 用数据集合中前K个元素来建堆
    前k个最大的元素,则建小堆
    前k个最小的元素,则建大堆
  2. 剩余的N-K个元素依次与堆顶元素来比较不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后堆中剩余的K个元素就是所求的前K个最小或者最大的元素

我们以寻找最大的前k个数为例:

我们先通过文件操作进行造数据:
忘记文件操作的朋友们可以看看这篇博客 文件操作(上)

void CreateNdata()
{
	const char* file = "data.txt";
	FILE* fq = fopen(file, "w");
	if (fq == NULL)
	{
		perror("malloc::fail");
		return;
	}
	srand(time(NULL));

	int n = 10000000;
	for (int i = 0; i < n; i++)
	{
		int ret = rand() % 10000;
		fprintf(fq, "%d\n", ret);
	}

	fclose(fq);
	free(fq);

}

大家会发现我们造的数据非常多,没错,就是要这种效果,现实生活中数据过多我们就不能用内存来存储,用文件来储存

接下来我们将这些数据的前k个进行建堆,并将剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素。一定要记得建的是小堆!

void PrintTopK(const char* file, int k)
{
	// 1. 建堆--用a中前k个元素建小堆
	int* topk = (int*)malloc(sizeof(int) * k);
	assert(topk);
	FILE* fq = fopen(file, "r");
	if (fq == NULL)
	{
		perror("malloc:fail");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fq, "%d", &topk[i]);
	}
	//建小堆
	for (int i = (k - 2) / 2; i >= 0; i--)
	{
		AdjustDown(topk, k, i);
	}
	int val = 0;
	//ret是fscanf的返回值,fscanf返回eof,则结束
	int ret = fscanf(fq, "%d", &val);
	while (ret != EOF)
	{
		if (val > topk[0])
		{
			swap(&val ,&topk[0]);
			AdjustDown(topk, k, 0);
		}
		ret = fscanf(fq, "%d", &val);
	}

	for (int i = 0; i < k; ++i)
	{
		printf("%d ", topk[i]);
	}
	printf("\n");

	fclose(fq);
	free(fq);
}

随后我们看看结果:
先创建数据:

int main()
{
	CreateNdata();
	//PrintTopK("data.txt", 10);
}

然后在取前k个数据:

int main()
{
	//CreateNdata();
	PrintTopK("data.txt", 10);
}

在这里插入图片描述
产生这个结果的原因是数据太多了,导致产生随机数9999的数据太多了。现在我们直接改改文件里的数据:
在这里插入图片描述
在改了以后最大的几个数一定会有最后几个,现在我们来看看结果:
在这里插入图片描述
这样最大的前k个数就以小堆的形式打印出来了。

总结

  更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

专栏订阅:
每日一题
c语言学习
算法
智力题
初阶数据结构
更新不易,辛苦各位小伙伴们动动小手,👍三连走一走💕💕 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

在这里插入图片描述

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

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

相关文章

Surfshark下载到使用完整教程|2023最新

2023年3月16日更新 在正式介绍surfshark的教程( 教程直达学习地址: qptool.net/shark.html )之前&#xff0c;我们可以来看看最近surfshark的服务与产品退化到什么程度了。我曾经是Surshark两年的忠实用户&#xff0c;但是&#xff0c;现在&#xff0c;作为一个负责人的测评&a…

PostMan动态参数及循环调用

最近需要在测试环境批量创建es索引&#xff0c;也就是某个接口需要循环调用且参数还是变化的&#xff0c;但是我又不想写代码和脚本&#xff0c;于是研究了一下postman一些好玩的功能&#xff0c;希望能节约大家的开发时间 一.设置请求参数 1.获取创建索引的请求以及参数&…

ELK+Filebeat日志分析系统

目录 一.ELK基本介绍 1.ELK是什么&#xff1f; 2.组件简介 2.1 ELK组件介绍 2.2 ELFK组件介绍 2.3 其它组件 4.使用ELK的原因 5.完整日志系统的基本特征 二.Elasticsearch的介绍 三.Logstash的介绍 四.Kibana的介绍 五.ELK的工作原理 六.部署ELK日志分析系统 1.环…

0基础学习软件测试有哪些建议

其实现在基础的资料和视频到处都是&#xff0c;就是看你有没有认真的去找学习资源了&#xff0c;去哪里学习都是要看你个人靠谱不靠谱&#xff0c;再好的教程和老师&#xff0c;你自己学习不进去也是白搭在正式选择之前&#xff0c;大可以在各种学习网站里面找找学习资源先自己…

springboot+vue动物园管理系统java

本系统使用的角色主要有系统管理员、注册用户&#xff0c;本系统分为系统前台和系统后台&#xff0c;首先在系统前台&#xff0c;游客用户可以经过账号注册&#xff0c;管理员审核通过后&#xff0c;用账号密码登录系统前台&#xff0c;查看论坛交流、动物展览、原生动物展览、…

HTML5 <head> 标签、HTML5 <i> 标签

HTML5 <head> 标签 实例 HTML5 <head> 标签表示文档的头部&#xff0c;其中包含了与该文档有关的信息&#xff01; 一份在头部带有 <title> 标签的 HTML 文档&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8&…

Linux信号sigaction / signal

Linux信号sigaction / signal 文章目录Linux信号sigaction / signal目的函数原型struct sigaction信号枚举值ISO C99 signals.Historical signals specified by POSIX.New(er) POSIX signals (1003.1-2008, 1003.1-2013).Nonstandard signals found in all modern POSIX system…

虹科教您 | 基于Linux系统的RELY-TSN-KIT套件操作指南(1)——硬件设备与操作环境搭建

RELY-TSN-KIT是一款针对TSN的开箱即用的解决方案&#xff0c;它可以无缝实施确定性以太网网络&#xff0c;并从这些技术复杂性中抽象出用户设备和应用。该套件可评估基于IEEE 802.1AS同步的时间常识的重要性&#xff0c;并借助时间感知整形器来确定性地交付实时流量&#xff0c…

判断完全二叉树(层序遍历)| C

层序遍历 基本思路&#xff1a;利用队列&#xff0c;出上一层&#xff0c;带下一层&#xff08;NULL不入队列&#xff09; &#xff08;C语言需要自己构建队列→【队列】&#xff1c;用链表实现队列&#xff1e; | [数据结构] | C语言&#xff09; 代码 #include "Queu…

代码自动发布系统

之前是jenkins发现gitlab代码更新了就自动获取直接部署到服务器 现在是jenkins自动获取Code之后打包成镜像上传到仓库然后通知docker去拉取更新的镜像 分析 旧∶ 代码发布环境提前准备&#xff0c;以主机为颗粒度静态 新: 代码发布环境多套&#xff0c;以容器为颗粒度编译 …

Typora设置修改字体颜色快捷键

目录 1.typora如何设置修改字体颜色快捷键 2. AutoHotKey软件安装 3.typora关于AutoHotKey的具体操作 1.typora如何设置修改字体颜色快捷键 typora本身是不能直接修改字体颜色的&#xff0c;不过若是想修改还是可以用一些代码去改变的&#xff0c;但是每次都修改一次实在麻烦…

mysql常用的基础命令

通过学习mysql命令提高数据处理和工作效率 基础命令 1.登录MySQL mysql -u root -p 2.查看当前系统所有数据库 show databases; 3.切换数据库 use 数据库名称 4.查看数据库下的所有表 show tables; 5.查看表结构&#xff1b; desc 表名&#xff1b; 6.创建数据库 crea…

MAC OS(M1)安装配置miniconda

一、下载安装miniconda miniconde官网&#xff1a;Miniconda — Conda documentation M1最低只能适配到python3.8 打开终端,进入安装包所在文件夹&#xff0c;使用命令进行安装 bash Miniconda3-latest-MacOSX-arm64.sh一路回车 二、配置环境 安装完成后重启终端&#xf…

Unity ads广告插件的使用

介绍 Unity Ads SDK 由领先的移动游戏引擎创建,无论您在 Unity、Xcode 还是 Android Studio 中进行开发,都能为您的游戏提供全面的货币化框架。 使用 Unity Ads 将各种广告格式合并到游戏中的自然呈现点中。例如,您可以实施激励视频广告来构建更强大的游戏经济,同时为您的…

[C++笔记]vector

vector vector的说明文档 vector是表示可变大小数组的序列容器(动态顺序表)。就像数组一样&#xff0c;vector也采用连续的存储空间来储存元素。这就意味着可以用下标对vector的元素进行访问&#xff0c;和数组一样高效。与数组不同的是&#xff0c;它的大小可以动态改变——…

1700页,卷S人的 Java《八股文》PDF手册,涨薪跳槽拿高薪就靠它了

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;又得准备面试了&#xff0c;不知道从何下手&#xff01; 不论是跳槽涨薪&#xff0c;还是学习提升&#xff01;先给自己定一个小目标&#xff0c;然后再朝着目标去努力就完事儿了&#xff01; 为了帮大家节约时间&a…

Mybatis一级缓存和二级缓存(带测试方法)

目录 一、什么是缓存 二、Mabtis一级缓存 &#xff08;1&#xff09;测试一级缓存 &#xff08;2&#xff09;清空一级缓存 三、Mybatis二级缓存 &#xff08;1&#xff09;开启二级缓存 &#xff08;2&#xff09;测试二级缓存 一、什么是缓存 缓存是内存当中一块存储数…

蓝桥杯嵌入式第十一届省赛题目解析

写完第十一届蓝桥杯嵌入式省赛题目&#xff0c;拿出来给大家参考参考&#xff0c;也是让大家一起测试看看有什么问题还需要改进&#xff0c;代码在最后喔。 目录 客观题&#xff1a; 程序设计题 &#xff1a; 题目解析&#xff1a; CubeMX配置 代码演示 &#xff1a; 客观…

Windows环境下实现设计模式——职责链模式(JAVA版)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows环境下如何编程实现职责链模式&#xff08;设计模式&#xff09;。 不知道大家有没有这样的感觉&#xff0c;看了一大堆编程和设计模式的书&#xff0c;却还是很难理解设计模式&#xff…

spring boot Websocket(使用笔记)

使用websocket有两种方式&#xff1a;1是使用sockjs&#xff0c;2是使用h5的标准。使用Html5标准自然更方便简单&#xff0c;所以记录的是配合h5的使用方法。 1、pom 核心是ServerEndpoint这个注解。这个注解是Javaee标准里的注解&#xff0c;tomcat7以上已经对其进行了实现&a…