【数据结构】顺序结构实现:特殊完全二叉树(堆)+堆排序

二叉树

  • 一.二叉树的顺序结构
  • 二.堆的概念及结构
  • 三.堆的实现
    • 1.堆的结构
    • 2.堆的初始化、销毁、打印、判空
    • 3.堆中的值交换
    • 4.堆顶元素
    • 5.堆向上调整算法:实现小堆的插入
    • 6.堆向下调整算法:实现小堆的删除
    • 7.堆的创建
      • 1.堆向上调整算法:建堆+建堆的时间复杂度:O(n * logn)
      • 1.堆向下调整算法:建堆+建堆的时间复杂度:O(n)
  • 四.堆的应用
    • 1.TOP-K问题
    • 2.堆排序+堆排序时间复杂度:O(n * logn)

一.二叉树的顺序结构

  普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(完全二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
在这里插入图片描述

二.堆的概念及结构

在这里插入图片描述
堆的性质:

  1. 堆中某个结点的值总是不大于或不小于其父结点的值;
  2. 堆总是一棵完全二叉树。

在这里插入图片描述
在这里插入图片描述

三.堆的实现

1.堆的结构

将堆(完全二叉树)看作顺序表,利用顺序表存储堆中的值。结构如下:

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

2.堆的初始化、销毁、打印、判空

实际就是顺序表的那一套。

void HPInit(HP* php)
{assert(php);php->a = NULL;php->size = php->capacity = 0;
}void HPDestory(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}void HPPrint(HP* php)
{assert(php);for (int i = 0; i < php->size; i++){printf("%d ", php->a[i]);}printf("\n");
}

3.堆中的值交换

void Swap(HPDataType* x, HPDataType* y)
{HPDataType tmp = *x;*x = *y;*y = tmp;
}

4.堆顶元素

HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}

5.堆向上调整算法:实现小堆的插入

堆的插入:在已知是堆的条件下进行尾插,利用堆向上调整算法使其依旧保持堆的特征。

堆向上调整算法:

  1. 先将元素插入堆的末尾。
  2. 插入之后如果堆的性质遭到破坏,将新插入的节点顺着父亲节点往上调整到合适的位置即可。

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

在这里插入图片描述

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 HPPush(HP* php, HPDataType x)
{assert(php);//容量满了——>扩容if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail!");return;}php->a = tmp;php->capacity = newcapacity;}//尾插php->a[php->size++] = x;//向上调整算法AdjustUp(php->a, php->size - 1);
}

6.堆向下调整算法:实现小堆的删除

堆的删除:在已知是堆的条件下删除堆顶的数据(堆的尾删无意义依旧是堆),先将堆顶与堆尾的数据进行交换,再删除堆尾的数据,最后利用堆向下调整算法,将堆顶向下调整,使其依旧保持堆的特征。

堆向下调整算法:

  1. 将堆顶元素与堆中最后一个元素进行交换。
  2. 删除堆中最后一个元素。
  3. 将堆顶元素向下调整到满足堆特性为止。

例如:现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是都是堆,才能调整

在这里插入图片描述

void AdjustDown(HPDataType* a, int n, int parent)
{//假设左孩子小int child = 2 * parent + 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;}}
}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);
}

7.堆的创建

1.堆向上调整算法:建堆+建堆的时间复杂度:O(n * logn)

//假设模拟php->a[] = {19,34,65,18,15,28}; 

在这里插入图片描述

void AdjustUpCreateHeap(HP* php)
{//根节点不需调整,从第二个节点开始调整for (int i = 1; i < php->size; i++){AdjustUp(php->a, i);}
}
int main()
{HP hp;HPInit(&hp);hp.a = (HPDataType*)malloc(sizeof(HPDataType) * 6);hp.a[0] = 19;hp.a[1] = 34;hp.a[2] = 65;hp.a[3] = 18;hp.a[4] = 15;hp.a[5] = 28;hp.size += 6;hp.capacity += 6;AdjustUpCreateHeap(&hp);HPPrint(&hp); //15 18 28 34 19 65
}

计算向上调整算法建堆时间复杂度:

因为堆是完全⼆叉树,而满⼆叉树也是完全⼆叉树,此处为了简化使用满⼆叉树来证明(时间复杂度本来看的就是近似值,多几个结点不影响最终结果)
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

1.堆向下调整算法:建堆+建堆的时间复杂度:O(n)

//假设模拟php->a[] = {19,34,65,18,15,28}; 

在这里插入图片描述

void AdjustDownCreateHeap(HP* php)
{//叶子节点不需调整,从倒数第一个非叶子节点开始调整for (int i = (php->size - 1 - 1) / 2; i >= 0; i--){AdjustDown(php->a, php->size, i);}
}
int main()
{HP hp;HPInit(&hp);hp.a = (HPDataType*)malloc(sizeof(HPDataType) * 6);hp.a[0] = 19;hp.a[1] = 34;hp.a[2] = 65;hp.a[3] = 18;hp.a[4] = 15;hp.a[5] = 28;hp.size += 6;hp.capacity += 6;AdjustDownCreateHeap(&hp); //15 18 28 19 34 65HPPrint(&hp);
}

计算向下调整算法建堆时间复杂度:

在这里插入图片描述
在这里插入图片描述

四.堆的应用

1.TOP-K问题

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

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

前置知识:

  1. 1 GB = 1024 MB = 1024*1024 KB = 1024*1024*1024 Byte(大约10.7亿Byte)
  2. 40Byte转化为GB:40 / 10.7 近似:3.72GB内存

问题一:假设只有4GB内存,要在10亿个整数中,如何找出最大的前10个数?

方法:

  1. 建一个10亿个整数的大堆(利用向下调整算法)。时间复杂度:O(n)
  2. 进行10次(Top+Pop):取最大的前10个数。时间复杂度:O(10 * logn) = O(logn)(10忽略不计)

总时间复杂度:O(n) ; 空间复杂度:O(n)

问题二:假设只有1GB内存,在这些磁盘文件中,取最大的前10个数。

方法:

  1. 先存储1GB内存的数据,再取最大前10个数,依次循环4次
  2. 最后在40个数据中取最大的前10个数

虽然节省了一些内存,但是依旧要花费相当大的内存,而且频繁的读取数据效率低。有无不需要花费多少内存就能完成的呢?

问题三:假设只有1KB内存,在这些磁盘文件中,取最大的前10个数。

方法:

  1. 用数据集合中前K个元素来建堆:
  • 取前K个最大的元素,则建小堆
  • 取前K个最小的元素,则建大堆

时间复杂度:O(k)

  1. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素。将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

时间复杂度:O((n-k) * logk)

总时间复杂度:O(n) ;空间复杂度:O(1);(k忽略不计)

代码如下:

void CreateNDate()
{//写入n个数值int n = 10000000;srand((unsigned int)time(NULL));//以写的方式打开文件const char* file = "data.txt";FILE* pf = fopen(file, "w");if (pf == NULL){perror("fopen fail!");return;}//写数据进入文件for (int i = 0; i < n; i++){int val = (rand() + i) % 10000000;fprintf(pf, "%d\n", val);}//关闭文件fclose(pf);pf = NULL;
}void TopK()
{//以读的方式打开文件const char* file = "data.txt";FILE* pf = fopen(file, "r");if (pf == NULL){perror("fopen fail!");return;}//开K个空间:为建小堆做准备int k;printf("请输入要取的最大前k个数:");scanf("%d", &k);int* kminheap = (int*)malloc(sizeof(int) * k);if (kminheap == NULL){perror("malloc fail!");return;}//读取文件中的前K个数据for (int i = 0; i < k; i++){fscanf(pf, "%d", &kminheap[i]);}//建K个数的小堆(向下调整算法)for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(kminheap, k, i);}//取剩下的N-K个数与堆顶进行比较+向下调整int val;while (fscanf(pf, "%d", &val) != EOF){if (val > kminheap[0]){kminheap[0] = val;AdjustDown(kminheap, k, 0);}}printf("最大的前%d个数:", k);for (int i = 0; i < k; i++){printf("%d ", kminheap[i]);}printf("\n");//关闭文件fclose(pf);pf = NULL;
}int main()
{CreateNDate();TopK();return 0;
}

在这里插入图片描述

2.堆排序+堆排序时间复杂度:O(n * logn)

假设降序:那是建小堆还是建大堆呢?

结论:

  1. 升序:建大堆。
  2. 降序:建小堆。

利用堆删除思想来进行排序:建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

假设6个数据建小堆:

  1. 已知堆顶最小,可以将堆顶与队尾进行交换。
  2. 向下调整保持前5个数据为小堆。
  3. 循环步骤1与步骤2直到排序完成。

在这里插入图片描述

void HeapSort(HPDataType* 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);}//O(n*logn)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}
int main()
{int a[] = { 1,5,3,6,8,9,2,4,0,7 };HeapSort(a, sizeof(a) / sizeof(a[0]));for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++){printf("%d ", a[i]);}printf("\n");
}

在这里插入图片描述

堆排序时间复杂度计算:

在这里插入图片描述

重点理解:向下调整算法。

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

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

相关文章

CentOS 安装Redis

在 CentOS 安装 Redis 操作系统&#xff1a;centos-7.9.2009-Core 1. 更新系统 首先&#xff0c;确保你的系统是最新的&#xff1a; sudo yum update -y2. 安装 EPEL 仓库 Redis 可能不在默认的 CentOS 仓库中&#xff0c;因此你需要安装 EPEL&#xff08;Extra Packages f…

TCP详解及其在音视频传输中的应用

传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是互联网协议栈中至关重要的传输层协议。它提供了可靠、面向连接的数据传输服务&#xff0c;广泛应用于各种网络应用中。对于音视频传输&#xff0c;虽然TCP协议并不是最常用的传输协议&…

LVS实验——部署DR模式集群

目录 一、实验环境 二、配置 1、LVS 2、router 3、client 4、RS 三、配置策略 四、测试 1.Director服务器采用双IP桥接网络&#xff0c;一个是VPP&#xff0c;一个DIP 2.Web服务器采用和DIP相同的网段和Director连接 3.每个Web服务器配置VIP 4.每个web服务器可以出外网…

《Advanced RAG》-11-RAG查询分类和细化

总结 文章介绍了两种高级的检索增强生成&#xff08;RAG&#xff09;技术&#xff1a;自适应 RAG 和 RQ-RAG&#xff0c;以及它们在问题复杂性学习和查询细化方面的应用和优势&#xff0c;以及如何通过小型模型的训练来提高这些技术的性能。 摘要 传统 RAG 技术虽然能够减少大型…

「MyBatis」数据库相关操作2

&#x1f387;个人主页 &#x1f387;所属专栏&#xff1a;Spring &#x1f387;欢迎点赞收藏加关注哦&#xff01; #{} 和 ${} 我们前面都是采用 #{} 对参数进行赋值&#xff0c;实际上也可以用 ${} 客户端发送⼀条 SQL 给服务器后&#xff0c;大致流程如下&#xff1a; 1.…

51单片机之动态数码管显示

一、硬件介绍 LED数码管是一种由多个发光二极管&#xff08;LED&#xff09;封装在一起&#xff0c;形成“8”字型的显示器件。它广泛用于仪表、时钟、车站、家电等场合&#xff0c;用于显示数字、字母或符号。 通过控制点亮a b c d e f g dp来显示数字&#xff0c;本实验开发板…

前端八股文笔记【三】

JavaScript 基础题型 1.JS的基本数据类型有哪些 基本数据类型&#xff1a;String&#xff0c;Number&#xff0c;Boolean&#xff0c;Nndefined&#xff0c;NULL&#xff0c;Symbol&#xff0c;Bigint 引用数据类型&#xff1a;object NaN是一个数值类型&#xff0c;但不是…

十三、代理模式

文章目录 1 基本介绍2 案例2.1 Sortable 接口2.2 BubbleSort 类2.3 SortTimer 类2.4 Client 类2.5 Client 类的运行结果2.6 总结 3 各角色之间的关系3.1 角色3.1.1 Subject ( 主体 )3.1.2 RealObject ( 目标对象 )3.1.3 Proxy ( 代理 )3.1.4 Client ( 客户端 ) 3.2 类图 4 动态…

Java网络编程、TCP、UDP、Socket通信---初识版

标题 InetAddress----IP地址端口号协议&#xff08;UDP/TCP&#xff09;JAVA操作-UDP一发一收模式多发多收 JAVA操作-TCP一发一收多发多收 实现群聊功能BS架构线程池优化 InetAddress----IP地址 端口号 协议&#xff08;UDP/TCP&#xff09; JAVA操作-UDP 一发一收模式 多发多收…

React 性能优化

使用 useMemo 缓存数据 &#xff08;类似 vue 的 computed&#xff09;使用 useCallback 缓存函数异步组件 ( lazy )路由懒加载( lazy )服务器渲染 SSR用 CSS 模拟 v-show 循环渲染添加 key使用 Fragment &#xff08;空标签&#xff09;减少层级 不在JSX 中定义函数&#xff0…

一篇教会搭建ELK日志分析平台

日志分析的概述 日志分析是运维工程师解决系统故障&#xff0c;发现问题的主要手段日志主要包括系统日志、应用程序日志和安全日志系统运维和开发人员可以通过日志了解服务器软硬件信息、检查配置过程中的错误及错误发生的原因经常分析日志可以了解服务器的负荷&#xff0c;性…

使用本地大模型从论文PDF中提取结构化信息

1 安装ollama 点击前往网站 https://ollama.com/ &#xff0c;下载ollama软件&#xff0c;支持win、Mac、linux 2 下载LLM ollama软件目前支持多种大模型&#xff0c; 如阿里的&#xff08;qwen、qwen2&#xff09;、meta的(llama3、llama3.1)&#xff0c; 读者根据自己电脑…

C语言:求最大数不用数组

&#xff08;1&#xff09;题目&#xff1a; 输入一批正数用空格隔开&#xff0c;个数不限&#xff0c;输入0时结束循环&#xff0c;并且输出这批整数的最大值。 &#xff08;2&#xff09;代码&#xff1a; #include "stdio.h" int main() {int max 0; // 假设输入…

Qt——多线程

一、QThread类 如果要设计多线程程序&#xff0c;一般是从QThread继承定义一个线程类&#xff0c;并重新定义QThread的虚函数 run() &#xff0c;在函数 run() 里处理线程的事件循环。 应用程序的线程称为主线程&#xff0c;创建的其他线程称为工作线程。主线程的 start() 函数…

计算机网络408考研 2014

1 计算机网络408考研2014年真题解析_哔哩哔哩_bilibili 1 111 1 11 1

MyBatis:Maven,Git,TortoiseGit,Gradle

1&#xff0c;Maven Maven是一个非常优秀的项目管理工具&#xff0c;采用一种“约定优于配置&#xff08;CoC&#xff09;”的策略来管理项目。使用Maven不仅可以把源代码构建成可发布的项目&#xff08;包括编译、打包、测试和分发&#xff09;&#xff0c;还可以生成报告、生…

短视频SDK,支持Flutter跨平台框架,加速产品上线进程

在数字内容爆炸式增长的今天&#xff0c;短视频已成为连接用户、传递情感、展现创意的重要桥梁。为助力开发者快速融入这股潮流&#xff0c;美摄科技匠心打造了一款专为Flutter框架优化的短视频SDK解决方案&#xff0c;旨在降低技术门槛&#xff0c;加速产品迭代&#xff0c;让…

主题与分区

主题和分区是Kafka的两个核心概念&#xff0c;分区的划分不仅为Kafka提供了可伸缩性、水平扩展的功能&#xff0c;还通过多副本机制来为Kafka提供数据冗余以提高数据可靠性。 主题创建 主题和分区都是提供给上层用户的抽象&#xff0c;而在副本层面或更加准确地说是Log层面&a…

Unity效果优化之抗锯齿

Unityde 基于HDRP渲染管线的抗锯齿处理的设置参考图&#xff1a; 前提&#xff1a;需要导入HDRP的插件包才行&#xff0c; 该参数设置能保证在PC版上抗锯齿效果非常好&#xff0c; 英文版&#xff1a;

AWS Lambda 十年回顾:功能总览、更新记录与入门指南

这次&#xff0c;我为2014年11月发布的AWS Lambda创建了一个历史时间表。AWS Lambda 是一项无服务器、全托管的代码执行服务&#xff0c;今年2024年11月将迎来其宣布发布的十周年纪念。虽然提前了一些&#xff0c;但为了提前庆祝这一重要时刻&#xff0c;我写了这篇文章。 文章…