【数据结构】排序效率最优解之一:二叉树-堆

Hello everybody!今天打算给大家介绍一个功能比较强大的数据结构的基础,它不仅具有很高的应用价值而且排序效率很高。冒泡排序都知道叭,它的时间复杂度为O(n^2),而堆排序的时间复杂度为O(n*logn)。堆排序直接碾压冒泡排序。在c语言阶段,我曾给过大家qsort函数模拟实现的代码,我是以冒泡排序为底层逻辑实现的:时间复杂度为O(n^2)。而真正库文件中的qsort是以快排为底层逻辑实现的:时间复杂度为O(n*logn)。所以当我们排较长的数值时,肉眼可见的会发现自己模拟实现的qsort的效率远远不及库文件中的qsort。这就很好的体现了时间复杂度为O(n*logn)的数据结构的魅力所在!那好,废话不多说,我们直接开始叭!

1.二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

1.顺序结构

顺序结构存储就是使用数组来存储,一般使用数组只适合完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

2.链式存储

二叉树的链式存储结构是指,用链表来表示一颗二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。链式结构又分为二叉链和三叉链。在当前的知识将借助一般都是二叉链,后面的高阶数据结构如红黑树等会用到三叉链。

2.堆的基本结构

注意堆有大堆和小堆之分。

小堆:每个结点上的数据都比子结点上的数据小,故根结点为最小值。

大堆:每个结点上的数据都比子结点上的数据小,故根结点为最大值。

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>typedef int HPDataType;
typedef struct Heap {HPDataType* a;int size;int capacity;
}HP;

这是堆的结构。其中a为数组名,堆上的数全部存放在这个数组中。虽然用数组存放堆,但它们的逻辑结构为堆。

因为:每个父结点的下标*2+1得到其左边的子结点,父结点的下标*2+2得其右边得子结点。

当然也可以反过来推:不管是左边的子结点还是右边的子节点,它们的下标-1与2取整即可得到其父结点。因为小数部分会被省略!

void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);//插入数据并向上调整调整
void HeapPop(HP* php);//删除堆顶(根节点)int HeapSize(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);

这些是堆需要实现的一些接口。其中最核心的是HeapPush和HeapPop,因为涉及到数据的调整。

其他接口就比较简单,我也就简单介绍一下。

3.接口的实现

那我就按照小堆给大家实现叭!

3.1初始化&销毁

void HeapInit(HP* php) {assert(php);//检查php是否为空指针php->a = NULL;php->size = php->capacity = 0;
}void HeapDestroy(HP* php) {assert(php);free(php->a);//将a指向的动态空间还给操作系统php->a = NULL;//置空,避免出现野指针php->size = php->capacity = 0;
}

这两个接口比较简单,给出的注释比较详细。我就不过多赘述了!

3.2插入数据

void Swap(HPDataType* p1, HPDataType* p2) {int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDataType* a, HPDataType child) {int parent = (child - 1) / 2;//找父结点while (child > 0) {if (a[child] < a[parent]) {//当子结点小于父节点时,交换Swap(&a[child], &a[parent]);child = (child - 1) / 2;//找上一个子节点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);//动态开辟空间assert(tmp);//检查是否开辟成功php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);//向上调整
}

我大概说一下这个接口的思路:1.首先看到HeapPush,先检查动态数组的容量是否够用,不够用的话就扩容。检查容量过后进入AdjustUp

2.在AdjustUp中,可以通过子节点找父节点,如果不满足小堆的规则,那么父节点的数值和子节点的数值交换,然后下标继续向上调整。直到子节点的下标为零(到达根结点时)结束。

通过调试会发现,确实是一个堆,这个接口的功能正常。

3.3删除堆顶

void AdjustDown(HPDataType* a, int size, int parent) {int child = parent * 2 + 1;//通过根结点找子结点while (child<size) {if (child + 1 < size && a[child + 1] < a[child]) {//如果左孩子大于右孩子,用右孩子child++;}if (a[parent] > a[child]) {Swap(&a[parent], &a[child]);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);//根结点向下调整
}

3.4结点个数&访问根节点&判空

HPDataType HeapTop(HP* php) {assert(php);assert(php->size > 0);return php->a[0];
}int HeapSize(HP* php) {assert(php);return php->size;
}bool HeapEmpty(HP* php) {assert(php);return php->size == 0;
}

这三个接口就很简单啦!宝子们看懂应该没什么问题!

通过测试可以看到堆排序可以实现升序排列,并且效率更高!

4.代码

头文件:

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.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);//删除堆顶(根节点)int HeapSize(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);

源文件:

#include "Heap.h"
void HeapInit(HP* php) {assert(php);//检查php是否为空指针php->a = NULL;php->size = php->capacity = 0;
}void HeapDestroy(HP* php) {assert(php);free(php->a);//将a指向的动态空间还给操作系统php->a = NULL;//置空,避免出现野指针php->size = php->capacity = 0;
}
void Swap(HPDataType* p1, HPDataType* p2) {int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDataType* a, HPDataType child) {int parent = (child - 1) / 2;//找父结点while (child > 0) {if (a[child] < a[parent]) {//当子结点小于父节点时,交换Swap(&a[child], &a[parent]);child = (child - 1) / 2;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);//动态开辟空间assert(tmp);//检查是否开辟成功php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);//向上调整
}void AdjustDown(HPDataType* a, int size, int parent) {int child = parent * 2 + 1;while (child<size) {if (child + 1 < size && a[child + 1] < a[child]) {child++;}if (a[parent] > a[child]) {Swap(&a[parent], &a[child]);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->size > 0);return php->a[0];
}int HeapSize(HP* php) {assert(php);return php->size;
}bool HeapEmpty(HP* php) {assert(php);return php->size == 0;
}

测试文件:

#include "Heap.h"
int main() {int a[] = { 4,6,2,1,5,8,2,9 };HP hp;HeapInit(&hp);for (int i = 0; i < 8; i++) {HeapPush(&hp, a[i]);}while(!HeapEmpty(&hp)){printf("%d", HeapTop(&hp));HeapPop(&hp);}return 0;
}

5.结语

那今天的讨论就到这里喽!不知道大家是否有所收获呢?可以把自己的疑问发在评论区!

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

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

相关文章

OpenCV | 模版匹配

import cv2 #opencv读取的格式是BGR import numpy as np import matplotlib.pyplot as plt#Matplotlib是RGB %matplotlib inline 模版匹配 模版匹配和卷积原理很像&#xff0c;模版在原图像上从原点开始滑动&#xff0c;计算模版与&#xff08;图像被模版覆盖的地方&#xff…

开源vs闭源,大模型的未来在哪一边?

开源和闭源&#xff0c;两种截然不同的开发模式&#xff0c;对于大模型的发展有着重要影响。开源让技术共享&#xff0c;吸引了众多人才加入&#xff0c;推动了大模的创新。而闭源则保护了商业利益和技术优势&#xff0c;为大模型的商业应用提供了更好的保障。 那么&#xff0c…

内置函数【MySQL】

文章目录 MySQL 内置函数日期和时间函数字符串函数数学函数信息函数参考资料 MySQL 内置函数 MySQL 的内置函数主要分为以下几种&#xff1a; 字符串函数&#xff1a;用于对字符串进行操作&#xff0c;如连接、截取、替换、反转、格式化等。数值函数&#xff1a;用于对数值进…

Spark_Spark高阶特性

wscg filter导致断链 Codegen 向量化 simdjson Orc Parquet 支持批量读取 spark本身对parquet支持比较好&#xff0c;因为parquet

jquery 地址四级联级显示 不默认选择

代码效果 <body class"bgca"><img src"./files/joinTooBg.png" style"width: 100%;object-fit: cover;" alt""><!--填写申请资料--><section><div class"zi-liao"><h3 class"zong-h…

【CVE-2023-49103】ownCloud graphapi信息泄露漏洞(2023年11月发布)

漏洞简介 ownCloud owncloud/graphapi 0.2.x在0.2.1之前和0.3.x在0.3.1之前存在漏洞。graphapi应用程序依赖于提供URL的第三方GetPhpInfo.php库。当访问此URL时&#xff0c;会显示PHP环境的配置详细信息&#xff08;phpinfo&#xff09;。此信息包括Web服务器的所有环境变量&a…

在Mysql中,什么是回表,什么是覆盖索引,索引下推?

一、什么是回表查询&#xff1f; 通俗的讲就是&#xff0c;如果索引的列在 select 所需获得的列中&#xff08;因为在 mysql 中索引是根据索引列的值进行排序的&#xff0c;所以索引节点中存在该列中的部分值&#xff09;或者根据一次索引查询就能获得记录就不需要回表&#x…

【傻瓜级JS-DLL-WINCC-PLC交互】2.wincc使用C#开发的.net控件

思路 JS-DLL-WINCC-PLC之间进行交互&#xff0c;思路&#xff0c;先用Visual Studio创建一个C#的DLL控件&#xff0c;然后这个控件里面嵌入浏览器组件&#xff0c;实现JS与DLL通信&#xff0c;然后DLL放入到WINCC里面的图形编辑器中&#xff0c;实现DLL与WINCC的通信。然后PLC与…

微机原理_7

一、单项选择题(本大题共15小题&#xff0c;每小题3分&#xff0c;共45分。在每小题给出的四个备选项中&#xff0c;选出一个正确的答案,请将选定的答案填涂在答题纸的相应位置上。) 下列属于串行通信接口标准的有&#xff08;) A. PCI B. IDE C. USB D. EISA Intel 8086/8088…

echart 柱状图-bar

业务场景一 效果 业务组件调用代码 <template> <barCom :domId"1" :title"barComProps.title" :xAxisData"barComProps.xAxisData" :yAxisProps"barComProps.yAxisProps" :seriseData"barComProps.serise…

基于FactoryBean、实例工厂、静态工厂创建Spring中的复杂对象

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

在 vue3 中使用 Recorder 实现录音并上传(mp3、wav)兼容 PC 和移动端

一、Recorder 介绍 使用 Recorder插件可以在网页中进行录音。生成 blob 文件并可以自定义上传&#xff0c;同时&#xff0c;录音过程中会显示可视化波形&#xff0c;同时能够做到兼容PC端、Android、和iOS&#xff0c;十分好用&#xff01; Recorder github 首页 插件效果展…