【数据结构】二叉树(二)——顺序结构

在这里插入图片描述

前言
本篇博客讲解数组实现二叉树的顺序结构


文章目录

  • 一、二叉树的顺序结构及实现
    • 1.1 二叉树的顺序结构
    • 1.2 堆的概念
    • 1.3 堆的实现
      • 1.3.1 初始化堆
      • 1.3.2 向堆中插入元素
      • 1.3.3 从堆顶删除
      • 1.3.4 其他操作
      • 1.3.5 完整代码
        • Heap.h
        • Heap.c
    • 1.4 堆的应用
      • 1.4.1 堆排序
      • 1.4.2 TOP-K问题

一、二叉树的顺序结构及实现

1.1 二叉树的顺序结构

一般来说,顺序结构(数组)通常会用来实现完全二叉树,顺序结构用来实现不完全二叉树不是好的想法,因为会浪费许多空间。
在这里插入图片描述

1.2 堆的概念

堆(Heap)是一种特殊的树形数据结构,堆常常被用于优先队列的实现,因为它支持快速查找和删除具有最高或最低优先级的元素。堆分为最大堆和最小堆,这两者都是二叉堆的一种形式。同时堆是完全二叉树。

最大堆和最小堆:

  1. 最大堆(Max Heap): 在最大堆中,父节点的值大于或等于其每个子节点的值。这意味着树的根节点包含最大的值。

  2. 最小堆(Min Heap): 在最小堆中,父节点的值小于或等于其每个子节点的值。这样,树的根节点包含最小的值。

堆一般是一个完全二叉树,但并不要求是满二叉树。
在这里插入图片描述

1.3 堆的实现

// 堆的数据类型
typedef int HPDataType;// 堆的结构体
typedef struct Heap {HPDataType* a;      // 数组,用于存储堆的元素int size;           // 当前堆中的元素个数int capacity;       // 堆的容量,即数组的大小
} HP;

1.3.1 初始化堆

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

1.3.2 向堆中插入元素

这里以最小堆为例
在这里插入图片描述
代码实现:

//元素交换位置
void Swap(HPDataType* p1 , HPDataType* p2) {HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向上调整
void AdjustUp(HPDataType* a, int child) {int parent = (child - 1) / 2;//parent为父亲节点while (child > 0) {//a[child] < a[parent]小堆排序;a[child] > a[parent]大堆排序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 , newCapacity * sizeof(HPDataType));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);
}

1.3.3 从堆顶删除

删除堆顶元素的思路通常涉及将堆顶元素与堆中最后一个元素交换,然后删除最后一个元素,最后执行一次向下调整(AdjustDown)操作,以维护堆的性质。
在这里插入图片描述
代码实现:

//向下调整
void AdjustDown(int* a, int size, int parent) {int child = parent * 2 + 1;while (child < size) {//a[child + 1] < a[parent]小堆排序;a[child + 1] > a[parent]大堆排序if (child + 1 < size && a[child + 1] < a[child]) {++child;}//a[child] < a[parent]小堆排序;a[child] > a[parent]大堆排序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);
}

1.3.4 其他操作

//销毁堆
void HeapDestroy(HP* php) {assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}//获取堆顶元素
HPDataType HeapTop(HP* php) {assert(php);assert(php->a);return php->a[0];
}//元素个数
size_t HeapSize(HP* php) {assert(php);return php->size;
}//判断堆是否为空
bool HeapEmpty(HP* php) {assert(php);return php->size == 0;
}

1.3.5 完整代码

Heap.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>// 堆的数据类型
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);// 获取堆的大小
size_t HeapSize(HP* php);// 判断堆是否为空
bool HeapEmpty(HP* php);
Heap.c
#include "Heap.h"void HeapInit(HP* php) {assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}void HeapDestroy(HP* php) {assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}void Swap(HPDataType* p1 , HPDataType* p2) {HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDataType* a, int child) {int parent = (child - 1) / 2;while (child > 0) {//a[child] < a[parent]小堆排序;a[child] > a[parent]大堆排序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 , newCapacity * sizeof(HPDataType));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 AdjustDown(int* a, int size, int parent) {int child = parent * 2 + 1;while (child < size) {//a[child + 1] < a[parent]小堆排序;a[child + 1] > a[parent]大堆排序if (child + 1 < size && a[child + 1] < a[child]) {++child;}//a[child] < a[parent]小堆排序;a[child] > a[parent]大堆排序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);
}HPDataType HeapTop(HP* php) {assert(php);assert(php->a);return php->a[0];
}size_t HeapSize(HP* php) {assert(php);return php->size;
}bool HeapEmpty(HP* php) {assert(php);return php->size == 0;
}

1.4 堆的应用

1.4.1 堆排序

根据堆的特性,可以用堆来进行排序,过程如下:

  1. 建堆: 将待排序的数组构建成一个最大堆或最小堆。

  2. 排序: 利用删除堆顶元素的思想,交换堆顶元素(根节点)与数组末尾元素,然后重新调整堆,使其保持最大堆或最小堆的性质。这个步骤只需要执行 n - 1 次,其中 n 是数组的长度。每次执行后,最大(或最小)元素会被移到数组的末尾。重复步骤 2 直到整个数组有序。

在这里插入图片描述

代码实现: 基于堆的实现

#include "Heap.h"//降序
void HeapSort(int* a, int n) {//建小堆,向上调整,时间复杂度O(N*logN)for (int i = 1; i < n; i++) {AdjustUp(a, i);}向下调整,时间复杂度O(N)//for (int i = (n - 1 - 1) / 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;}
}int main() {int a[] = { 1,5,4,3,6,9,10 };int n = sizeof(a)/sizeof(a[0]);HeapSort(a, n);int i = 0;while (i < n) {printf("%d ",a[i]);i++;}return 0;
}

输出结果:
在这里插入图片描述


在建立堆时,有向上调整和向下调整两种方式:

	//向上调整,时间复杂度O(N*logN)for (int i = 1; i < n; i++) {AdjustUp(a, i);}//向下调整,时间复杂度O(N)for (int i = (n - 1 - 1) / 2; i >= 0; --i) {AdjustDown(a, n, i);}

在这里插入图片描述
向上调整的时间复杂度为 T ( h ) = 1 × 2 1 + 2 × 2 2 + 3 × 2 3 + . . . . . . + ( h − 1 ) × 2 ( h − 1 ) T(h)=1×2^1+2×2^2+3×2^3+......+(h-1)×2^{(h-1)} T(h)=1×21+2×22+3×23+......+(h1)×2(h1),通过错位相减后得到 T ( h ) = − ( 2 h − 1 ) + 2 h ( h − 1 ) + 1 T(h)=-(2^h-1)+2^{h(h-1)}+1 T(h)=(2h1)+2h(h1)+1
因为树的高度h与节点数N之间的关系是 2 h − 1 = N 2^h-1=N 2h1=N
h = l o g 2 ( N + 1 ) h=log_2(N+1) h=log2(N+1)。用N代替h得到,时间复杂度 T ( N ) = − N + ( N + 1 ) ( l o g 2 ( N + 1 ) − 1 ) + 1 , 近似于 O ( N ) = N ∗ l o g 2 N T(N)=-N+(N+1)(log_2(N+1)-1)+1,近似于O(N)=N*log_2N T(N)=N+(N+1)(log2(N+1)1)+1,近似于O(N)=Nlog2N
在这里插入图片描述
向下调整的时间复杂度为 T ( h ) = ( h − 1 ) × 2 1 + ( h − 2 ) × 2 2 + ( h − 3 ) × 2 3 + . . . . . . + 1 × 2 ( h − 1 ) T(h)=(h-1)×2^1+(h-2)×2^2+(h-3)×2^3+......+1×2^{(h-1)} T(h)=(h1)×21+(h2)×22+(h3)×23+......+1×2(h1),通过错位相减后得到 T ( h ) = 2 h − 1 − h T(h)=2^h-1-h T(h)=2h1h
同样,用N代替h得到,时间复杂度 T ( N ) = N − l o g 2 ( N + 1 ) , 近似于 O ( N ) = N T(N)=N-log_2(N+1),近似于O(N)=N T(N)=Nlog2(N+1),近似于O(N)=N

因此,得出结论,在使用堆进行排序时,利用向下调整法来构建堆更加高效。

1.4.2 TOP-K问题

Top-k 问题是在一组数据中找出前 k 个最大或最小的元素的问题。这个问题在实际应用中经常出现,例如在搜索引擎中找出前 k 个相关度最高的页面,或者在数据分析中找出前 k 个热门商品。

最直观的方法是对整个数据集进行排序,然后取前 k 个或后 k 个元素。这种方法的时间复杂度通常为 O(n log n),其中 n 是数据集的大小。这在 k 远小于 n 的情况下是不划算的。

我们可以通过维护一个大小为 k 的最小堆,可以在 O(n log k) 的时间内找到前 k 个最大元素。

方法如下:

  1. 创建一个大小为 k 的堆: 初始化一个大小为 k 的堆,如果是求前 k 个最小元素,则使用最大堆(大顶堆),如果是求前 k 个最大元素,则使用最小堆(小顶堆)。

  2. 将前 k 个元素插入堆中: 遍历数组的前 k 个元素,依次插入堆中。

  3. 遍历数组的剩余元素: 从第 k+1 个元素开始遍历数组(对于大量数据显然是不合适的,所以可以从文件中挨个读取),对于每个元素,如果它比堆顶元素大(或小,取决于是求最大还是最小元素),则替换堆顶元素,并重新调整堆,以保持堆的性质。

  4. 输出结果: 遍历堆中的元素,即为前 k 个最大或最小元素。

要在大量数据中找出前k个最大的数字,如果全部放到数组中,所需要的空间很大,因此我们可以生成一个包含大量随机数据的文件。
代码如下:

//头文件中需要包含的头文件
//#include <time.h>// 随机生成大量数据
void CreateDate() {int n = 100000;srand(time(0));const char* file = "date.txt";FILE* fin = fopen(file, "w");if (fin == NULL) {perror("fopen error");return;}for (int i = 0; i < n; i++) {//保证生成的随机数小于100000int x = (rand() + i) % 100000;fprintf(fin, "%d\n", x);}fclose(fin);
}// 从文件中读取数据并输出前k个最大值
void PrintfTopK(const char* file, int k) {FILE* fout = fopen(file, "r");if (fout == NULL) {perror("fopen error");return;}int* heap = (int*)malloc(sizeof(int) * k);if (heap == NULL) {perror("malloc error");return;}// 初始化堆for (int i = 0; i < k; i++) {fscanf(fout, "%d", &heap[i]);AdjustUp(heap, i);}int x = 0;// 逐个读取文件中的数据while (fscanf(fout, "%d", &x) != EOF) {// 如果读取的数比堆顶元素大,则更新堆if (x > heap[0]) {heap[0] = x;AdjustDown(heap, k, 0);}}// 输出前k个最大值for (int i = 0; i < k; i++) {printf("%d ", heap[i]);}free(heap);fclose(fout);
}// 主函数
int main() {// 生成随机数据文件CreateDate();// 输出文件中的前5个最大值PrintfTopK("date.txt", 5);return 0;
}

我们要查看所有的数据,依此判断是否成功找出前k个最大数是不容易的,所以我们在生成的文件中,将5个随机生成的数改成比100000大的数,当我们输出时,只需要检查最大的5个数是不是这几个,即可知道排序是否正确。
在这里插入图片描述
输出结果:
在这里插入图片描述


在这里插入图片描述
如果你喜欢这篇文章,点赞👍+评论+关注⭐️哦!
欢迎大家提出疑问,以及不同的见解。

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

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

相关文章

web服务器nginx和Apache有什么区别?

随着互联网的快速发展&#xff0c;Web服务器在互联网应用中扮演着越来越重要的角色。其中&#xff0c;Nginx和Apache是两种广泛使用的Web服务器软件。尽管它们都可以实现Web服务器的功能&#xff0c;但Nginx和Apache在许多方面存在一些重要的区别。本文将探讨Nginx和Apache之间…

大数据技术在民生资金专项审计中的应用

一、应用背景 目前&#xff0c;针对审计行业&#xff0c;关于大数据技术的相关研究与应用一般包括大数据智能采集数据技术、大数据智能分析技术、大数据可视化分析技术以及大数据多数据源综合分析技术。其中&#xff0c;大数据智能采集数据技术是通过网络爬虫或者WebService接…

Docker无法启动Postgresql容器

目录 问题描述解决问题 问题描述 拉取了一个Postgresql14.2的镜像&#xff0c;在docker run创建并运行容器之后使用docker ps发现容器没有跑起来&#xff0c;再次使用docker start也没跑起来。 docker run -d --name mypg -v psql-data:/var/lib/postgresql/data -e POSTGRES…

Python random模块(获取随机数)常用方法和使用例子

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 random.random random.random()用于生成一个0到1的随机符点数: 0 < n < 1.0 random.uniform random.uniform(a, b)&#xff0c;用于生成一个指定范围内的随机符点数&#xff0c;两个参数其中一个是上限&#xff0c;一…

2023-我的CSDN创作之旅

1.博客内容与数量 2023年共发表博客59篇&#xff0c;内容主要集中在GIS&#xff0c;空间分析等领域 主要内容有&#xff1a; networkx学习 Geospatial Data Science Geocomputation ESDA in PySal SHAP Spatial Data Analysis BikeDNA 以下是对这几个章节主要内容的简…

html+css 对input的使用以及详解

表单 form标签主要用于收集用户信息&#xff0c;对表单结果的处理和发送 使用场景&#xff1a;登录页面、注册页面、搜索区域 form属性描述action规定当提交表单时向何处发送表单数据method规定用于发送表单数据的 HTTP 方法name规定表单的名称target规定在何处打开 action …

矩阵式键盘按键值的数码管显示实验

#include<reg51.h> //包含51单片机寄存器定义的头文件 sbit P14P1^4; //将P14位定义为P1.4引脚 sbit P15P1^5; //将P15位定义为P1.5引脚 sbit P16P1^6; //将P16位定义为P1.6引脚 sbit P17P1^7; //将P17位定义为P1.7引脚 unsigned char code Tab[ ]…

CTF-PWN-栈溢出-高级ROP-【SROP】

文章目录 linux信息处理2017 360春秋杯 smallest检查源码思路第一次要执行ret时的栈执行write函数时修改rsp到泄露的栈地址上去 输入/bin/sh并sigreturn调用系统调用回忆exp注意一个离离原上谱的地方 参考链接 SROP(Sigreturn Oriented Programming) 于 2014 年被 Vrije Univer…

简单多状态dp问题(打家劫舍Ⅱ)

通过分类谈论&#xff0c;将环形的问题&#xff0c;转化成两个线性的 “ 打家劫舍Ⅰ ” 1.状态表示 2.状态转移方程 3.初始化 f[ 0 ] nums[ 0 ] g[ 0 ] 0 4.填表顺序 从左往右填表&#xff0c;两个表一块填 5.返回值 max( f[ n-1 ] , g [ n - 1 ] )

【Bug】Android BottomNavigationView 图标黑色色块问题

最近在研究Android Jetpack组件&#xff0c;在使用Navigation配合底部导航栏时&#xff0c;发现一个奇怪的问题&#xff0c;如下&#xff1a; 说明&#xff1a;图标来源于Iconfont开源图标库 我的第三个图标变成了一个黑色色块&#xff0c;这个问题前两天我遇见过&#xff0c…

.NetCore部署微服务(一)

目录 前言 什么是微服务 微服务的优势 微服务的原则 创建项目 在Docker中运行服务 客户端调用 简单的集群服务 前言 写这篇文章旨在用最简单的代码阐述一下微服务 什么是微服务 微服务描述了从单独可部署的服务构建分布式应用程序的体系结构流程&#xff0c;同时这些…

C# 使用Microsoft消息队列(MSMQ)

写在前面 Microsoft Message Queuing (MSMQ) 是在多个不同的应用之间实现相互通信的一种异步传输模式&#xff0c;相互通信的应用可以分布于同一台机器上&#xff0c;也可以分布于相连的网络空间中的任一位置。 使用消息队列可以实现异步通讯&#xff0c;无需关心接收端是否在…

海康威视摄像头+服务器+录像机配置校园围墙安全侦测区域入侵侦测+越界侦测.docx

一、适用场景 1、校园内&#xff0c;防止课外时间翻越围墙到校外、从校外翻越围墙到校内&#xff1b; 2、通过服务器摄像头的侦测功能及时抓图保存&#xff0c;为不安全因素提供数字化依据&#xff1b; 3、网络录像机保存监控视频&#xff0c;服务器保存抓拍到的入侵与越界&am…

UI自动化Selenium iframe切换多层嵌套

IFRAME是HTML标签&#xff0c;作用是文档中的文档&#xff0c;或者浮动的框架(FRAME)。iframe元素会创建包含另外一个文档的内联框架(即行内框架)。 简单来说&#xff0c;就像房子内的一个个房间一样&#xff1b;你要去房间里拿东西&#xff0c;就得先开门&#xff1b; 如上图…

出现 No such instance field: ‘XXXX‘ 的解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 作为一个全栈的开发玩家,需要调试前后端的数据传输,方便发现问题所在! 在debug整个项目的时候,检查传输数据的时候,发现前端可以传输,但是后端一直拿不到 出现如下问题:No such instance field: parentModel 截图…

UI5与后端的文件交互(四)

文章目录 前言一、后端开发1. 新建管理模板表格2. 新建Function&#xff0c;动态创建文档 二、修改UI5项目1.Table里添加下载证明列2. 实现onClickDown事件 三、测试四、附 前言 这系列文章详细记录在Fiori应用中如何在前端和后端之间使用文件进行交互。 这篇的主要内容有&…

Leetcode的AC指南 —— 字符串/卡码网:55. 右旋字符串

摘要&#xff1a; Leetcode的AC指南 —— 字符串/卡码网&#xff1a;55. 右旋字符串。题目介绍&#xff1a;字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k&#xff0c;请编写一个函数&#xff0c;将字符串中的后面 k 个字…

灸哥问答:数据结构对软件开发的作用

在软件开发的浩瀚海洋中&#xff0c;数据结构如同一座坚固的灯塔&#xff0c;为开发者指明方向&#xff0c;确保他们在构建复杂系统时不会迷失。数据结构不仅仅是编程的基础&#xff0c;更是高效、稳定、可扩展软件的核心。 一、提升算法效率 数据结构与算法紧密相连&#xf…

antd——a-date-picker——日期的限制功能——js基础积累

antd——a-date-picker——日期的限制功能——js基础积累 禁用日期一、限制只能选明天及之后的日期&#xff08;今天不可选中&#xff09;二、限制只能选今天及之后的日期&#xff08;今天可选中&#xff09;三、限制只能选昨天及之前的日期&#xff08;今天不可选中&#xff0…

Java业务功能并发问题处理

业务场景&#xff1a; 笔者负责的功能需要调用其他系统的进行审批&#xff0c;而接口的调用过程耗时有点长&#xff08;可能长达10秒&#xff09;&#xff0c;一个订单能被多个人提交审批&#xff0c;当订单已提交后会更改为审批中&#xff0c;不能再次审批&#xff08;下游系…