01数据结构-堆排序

01数据结构-堆排序

  • 前言
  • 1.堆
  • 2.堆的操作逻辑
  • 3.堆的代码实现

前言

数据结构中的堆是一种结构,C语言的堆是空间管理的程序员malloc,free的空间,两者没多大关系。

1.堆

  • 逻辑上

    堆(Heap)是一类基于完全二叉树的特殊数据结构。通常将堆分为两种类型:

    1.大顶堆(Max Heap):在大顶堆中,根结点的值必须大于他的孩子结点的值,对于二叉树中所有子树都满足此规律
    在这里插入图片描述

    2.小顶堆(Min Heap):在小顶堆中,根结点的值必须小于他的孩子结点的值,对于二叉树中所有子树都满足此规律
    在这里插入图片描述

  • 物理上

    之前有个性质:完全二叉树中用数组空间来存储结构,从1号下标开始存储第一个元素,i号下标的根节点是2\i,左孩子2i,右孩子2i+1,接下来我们来看下怎么处理堆的逻辑。

2.堆的操作逻辑

如图,有8个数组组成了最小堆初始化数,我已经申请了9个空间(因为是完全二叉树用数组空间存储,从1号下标开始存元素),定义两个 指针:len指针用来表示待插入位置,capacity用来表示数组空间容量
在这里插入图片描述
插入逻辑:

1.在最后一个位置上插入新元素,从代码角度讲 ++len;data[len]=v;
2.如果是小顶堆,找这个元素的父节点,如果父节点的值大于这个元素,交换两个节点的值
3.继续交换节点的父节点继续执行条件判断,是否交换
4.直到找到根或者已不满足条件

2,3,4我们的称为上浮操作

如图初始化时把9放进来
在这里插入图片描述
把3放进来,发现3比9小,于是交换两个节点的值,并在数据区里也更新对应的值
在这里插入图片描述
把7放进来
在这里插入图片描述
把6放进来,发现父节点9的值比它大,交换

在这里插入图片描述
一直这样直到把全部数字都放进堆中,最终结果如图

在这里插入图片描述
提取数据:
1.提取根元素,提取到最值
2.我们不能直接给它删了,我们要继续保持这颗树的逻辑结构
3.把最后一个元素放到根节点的值,有效长度再减1,等价于删除最后一个元素(因为删除完全二叉树的节点时都是删除叶子节点,这样才可以保持树的结构,但是没有满足小顶堆的约束)。
4.把现在的根节点的值放入到正确的位置

3,4我们称为下沉。

如图我们要提取根节点1,按照上述逻辑,我们要保持整棵树的结构,先把叶子节点9的值交给1,删除叶子节点,len–
在这里插入图片描述
开始完成约束条件,把根节点的值放入正确的位置,此时我们需要比较根节点的左右孩子的大小,选择较小的那个,因为如果我们把9和3交换位置,3比2大依然没有满足小顶堆的约束,所以9和2交换位置,此时9来到2的位置,再次比较左右孩子的大小,发现5是较小值,交换5和9。如图:
在这里插入图片描述
如果我们有100个元素,但是我们只想要提取前5个,你可以把这100个排好序,然后取前5个即可,但是这样时间复杂度是O(n2),我们完全可以用小顶堆排好这100个元素,然后提取5次小顶堆的根节点,时间开销就很少,这样效率就较好。例如应用:Top10,在音乐榜单中的前10首歌曲,就可以采取这种思想,把成千上万首歌放进小顶堆,然后取10次小顶堆的根节点就拍好序了。

3.堆的代码实现

数据结构实现

#ifndef MINI_HEAP_H
#define MINI_HEAP_H
#include "../sortHelper.h"
// 定义小顶堆的结构
typedef struct {keyType *data;int len;            // 堆data的长度int capacity;       // 最大容量
} MiniHeap;MiniHeap *createMiniHeap(int n);            // 产生n个元素的堆空间
void releaseMiniHeap(MiniHeap *miniHeap);// 插入
void insertMiniHeap(MiniHeap *heap, keyType e);
// 提取
keyType extractMinMiniHeap(MiniHeap *heap);
#endif //MINI_HEAP_H

创建树的接口:MiniHeap *createMiniHeap(int n);

MiniHeap * createMiniHeap(int n) {MiniHeap * heap = malloc(sizeof(MiniHeap));if (heap==NULL) {return NULL;}heap->data = malloc(sizeof(int)*n);if (heap->data == NULL) {free(heap);return NULL;}heap->capacity=n;heap->len=0;return heap;
}

释放树的接口:void releaseMiniHeap(MiniHeap *miniHeap);

void releaseMiniHeap(MiniHeap *miniHeap) {if (miniHeap) {if (miniHeap->data) {free(miniHeap->data);}free(miniHeap);}
}

插入接口:void insertMiniHeap(MiniHeap *heap, keyType e);

static void shiftUp(MiniHeap *miniHeap, int k) {keyType tmp;// k/2表示父节点,k节点子节点while (k > 1 && miniHeap->data[k / 2] > miniHeap->data[k]) {tmp = miniHeap->data[k];miniHeap->data[k] = miniHeap->data[k / 2];miniHeap->data[k / 2] = tmp;k /= 2;}
}void insertMiniHeap(MiniHeap *heap, keyType e) {//防越界if (heap->len + 1 > heap->capacity) {return;}heap->data[++heap->len] = e;shiftUp(heap,heap->len);
}

我这里写了一个内在的接口shifUp即我在2.堆的操作逻辑中的2,3,4操作中的上浮操作,相信大家应该能看懂

提取接口:keyType extractMinMiniHeap(MiniHeap *heap);

static void shiftDown(MiniHeap *miniHeap, int k) {keyType tmp;while (2 * k <= miniHeap->len) {        // 保证有左孩子int index = 2 * k;if (index + 1 <= miniHeap->len && miniHeap->data[index + 1] < miniHeap->data[index]) {++index;}if (miniHeap->data[k] <= miniHeap->data[index]) {break;}tmp = miniHeap->data[k];miniHeap->data[k] = miniHeap->data[index];miniHeap->data[index] = tmp;k = index;}
}keyType extractMinMiniHeap(MiniHeap *heap) {//防越界if (heap->len <= 0) {return 0;}keyType ret=heap->data[1];heap->data[1]=heap->data[heap->len--];shiftDown(heap,1);return ret;
}

注意在static void shiftDown(MiniHeap *miniHeap, int k)我们不仅要检验左子树有没有越界还要检验右子树有没有越界,先保证有左孩子,如果我们的右孩子没有越界并且右孩子的值小于左孩子的值,那就拿到右孩子的索引,如果父节点的值已经小于了左右孩子节点中的较小值就直接退出,然后进行交换值。

来测一下:

#include <stdio.h>
#include "miniHeap.h"void test01() {int data[] = {9, 3, 7, 6, 5, 1, 10, 2};int n = 20;MiniHeap *miniHeap = createMiniHeap(n);for (int i = 0; i < sizeof(data) / sizeof(data[0]); i++) {insertMiniHeap(miniHeap, data[i]);}printf("extra: ");for (int i = 0; i < sizeof(data) / sizeof(data[0]); i++) {printf("%d ", extractMinMiniHeap(miniHeap));}printf("\n");releaseMiniHeap(miniHeap);
}int main() {test01();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\04_Sort\HeapSort.exe
extra: 1 2 3 5 6 7 9 10进程已结束,退出代码为 0

我们再写一个接口来测试堆排序的时间复杂度:

#include "heapSort.h"void miniHeapSort(SortTable *table) {MiniHeap *heap = createMiniHeap(table->length);for (int i = 0; i < table->length; i++) {insertMiniHeap(heap, table->data[i].key);}for (int i = 0; i < table->length; i++) {table->data[i].key = extractMinMiniHeap(heap);}releaseMiniHeap(heap);
}

用快速排序来进行对比:

#include <stdio.h>
#include "miniHeap.h"
#include "heapSort.h"
#include "../02_SwapSort/quickSort.h"void test01() {int data[] = {9, 3, 7, 6, 5, 1, 10, 2};int n = 20;MiniHeap *miniHeap = createMiniHeap(n);for (int i = 0; i < sizeof(data) / sizeof(data[0]); i++) {insertMiniHeap(miniHeap, data[i]);}printf("extra: ");for (int i = 0; i < sizeof(data) / sizeof(data[0]); i++) {printf("%d ", extractMinMiniHeap(miniHeap));}printf("\n");releaseMiniHeap(miniHeap);
}void test02() {int n = 100000;SortTable *table1 = generateRandomArray(n, 0, n + 5000);SortTable *table2 = copySortTable(table1);testSort("Heap Sort", miniHeapSort, table1);testSort("quick Sort", quickSortV1, table2);releaseSortTable(table1);releaseSortTable(table2);
}int main() {test02();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\04_Sort\HeapSort.exe
Heap Sort cost time: 0.013000s.
quick Sort cost time: 0.009000s.进程已结束,退出代码为 0

可以看到两者的排序时间是差不多的,这是因为堆排序和树的高度有关,所以时间复杂度也是O(nlogn)。

大概先写这些吧,今天的博客就先写到这,谢谢您的观看。

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

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

相关文章

在线课程|基于SprinBoot+vue的在线课程管理系统(源码+数据库+文档)

在线课程 目录 基于SprinBootvue的在线课程管理系统 一、前言 二、系统设计 三、系统功能设计 1 管理员模块的实现 2在线课程 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|…

Python海象运算符:=

文章目录简介​​条件判断优化循环控制简化​推导式高效计算​正则匹配与数据提取​性能对比参考文献简介 海象运算符 :&#xff0c;又称​​赋值表达式​​&#xff08;Assignment Expression&#xff09;&#xff0c;Python 3.8 后可用&#xff0c;PEP 572 引入&#xff0c;…

Vue 2 项目中快速集成 Jest 单元测试(超详细教程)

在 Vue 项目中编写单元测试&#xff0c;是提升代码质量和维护性的关键一步。本文将带你从零开始&#xff0c;在一个 Vue 2 Vue CLI 项目中集成 Jest 作为单元测试框架&#xff0c;并运行第一个测试用例。✅ 适用于 Vue 2 项目&#xff08;如你使用的是 vue-cli-service&#x…

PostgreSQL15——管理表空间

管理表空间一、基本概念二、创建表空间三、修改表空间四、删除表空间一、基本概念 在 PostgreSQL 中&#xff0c;它是通过表空间&#xff08;Tablespaces&#xff09;来实现逻辑对象&#xff08;表、索引等&#xff09;与物理文件之间的映射。创建数据库或者数据表&#xff08…

趣打印高级版--手机打印软件!软件支持多种不同的连接方式,打印神器有这一个就够了!

软件介绍&#xff08;文末获取&#xff09;趣打印高级版是一款手机打印软件。软件支持五种不同的连接方式&#xff0c;每种都有稳定且快速的反应&#xff0c;用户均可通过手机进行打印机的远程使用和设置。软件还支持上传不同格式的文档类型进行打印&#xff0c;方便快捷&#…

【开源框架】7 款流行的 Vue 3 后台管理框架对比

以下是 7 个流行的 Vue 3 后台管理框架在 Star 数&#xff08;截至 2025 年 8 月21日的 GitHub 最新数据&#xff09;、框架特点、基于的技术栈及开源协议四个方面的详细对比&#xff1a; 1. Vue-Vben-Admin GitHub 地址&#xff1a;https://github.com/vbenjs/vue-vben-admin…

Datawhale工作流自动化平台n8n入门教程(一):n8n简介与平台部署

前言 在数字化时代&#xff0c;重复性的工作任务正在消耗着我们大量的时间和精力。从数据同步到营销自动化&#xff0c;从客户服务到内容管理&#xff0c;这些琐碎但必要的任务往往让我们疲于应对。而工作流自动化工具的出现&#xff0c;为我们提供了一个优雅的解决方案。 今天…

SRE - 定位与能力

仅为个人知识总结与记录 Site Reliability Engineer&#xff1a;站点可靠性工程&#xff08;SRE 软件工程师 运维专家 可靠性专家&#xff09; 相对传统的运维工程师&#xff0c;SER 注重开发&#xff0c;效率&#xff0c;追求自动化。对于 SRE 工程师&#xff0c;追究的就是…

StarRocks学习4-查询优化与性能调优

✅ 1. 执行计划分析&#xff08;EXPLAIN&#xff09; &#x1f31f; 作用&#xff1a; 用于查看 SQL 的执行路径&#xff0c;判断是否命中索引、物化视图、Join 策略、并行度等。 &#x1f4cc; 常用命令&#xff1a; EXPLAIN SELECT ...; EXPLAIN VERBOSE SELECT ...;&#x1…

CentOS系统安装Git全攻略

文章目录✅ 方法一&#xff1a;使用 yum 或 dnf 包管理器安装&#xff08;推荐&#xff09;1. 更新系统软件包(非必须)[^1]2. 安装 Git3. 验证安装✅ 方法二&#xff1a;从源码编译安装&#xff08;适用于需要自定义版本或配置&#xff09;1. 安装依赖包2. 下载 Git 源码3. 编译…

VR交通安全学习机-VR交通普法体验馆方案

VR交通安全学习机是一种基于虚拟现实技术的互动式教育设备&#xff0c;旨在通过虚拟环境模拟真实的交通场景&#xff0c;帮助用户深入了解交通规则、交通信号、道路安全等知识&#xff0c;并通过沉浸式的体验让他们亲身感受到不遵守交通规则的后果。无论是驾驶员、行人还是骑行…

算法题(188):团伙

审题&#xff1a; 本题需要我们通过解析所有人之间的关系&#xff0c;从而判断出朋友团体的总个数并输出 思路&#xff1a; 方法一&#xff1a;扩展域并查集 由于这里涉及对朋友/敌人等关系集合的频繁操作&#xff0c;所以我们需要使用并查集来操作&#xff0c;但是普通的并查集…

C++开发/Qt开发:单例模式介绍与应用

单例模式是软件设计模式中最简单也是最常用的一种创建型设计模式。它的核心目标是确保一个类在整个应用程序生命周期中只有一个实例&#xff0c;并提供一个全局访问点。笔者白话版理解&#xff1a;你创建了一个类&#xff0c;如果你希望这个类对象在工程中应用时只创建一次&…

Linux笔记---策略模式与日志

1. 设计模式设计模式是软件开发中反复出现的问题的通用解决方案&#xff0c;它是一套套被反复使用、多数人知晓、经过分类编目的代码设计经验总结。设计模式并非具体的代码实现&#xff0c;而是针对特定问题的抽象设计思路和方法论。它描述了在特定场景下&#xff0c;如何组织类…

关于多个el-input的自动聚焦,每输入完一个el-input,自动聚焦到下一个

讲解原理或者思路&#xff1a;如果你有多个el-input,想要实现每输入完一个输入框&#xff0c;然后自动聚焦到下一个输入框&#xff0c;同理&#xff0c;如果每删除一个输入框的值&#xff0c;自动聚焦到上一个输入框。条件那么首先要做的就是&#xff0c;设置条件&#xff0c;在…

AI 赋能教育变革:机遇、实践与展望

引言说明教育在社会发展中的重要地位&#xff0c;以及传统教育面临的困境。引出 AI 技术为教育变革带来新机遇&#xff0c;阐述研究其在教育中应用的价值。AI 为教育带来的机遇个性化学习支持&#xff1a;讲解 AI 通过分析学生学习数据&#xff0c;如答题情况、学习时间等&…

(一)八股(数据库/MQ/缓存)

文章目录 项目地址 一、数据库 1.1 事务隔离级别 1. 事务的四大特性 2. Read Uncommited脏读(未提交读) 3. Read Commited幻读(sql默认已提交读) 4. Repeatable Read 5. Serializable 6. Snapshot(快照隔离) 7. 代码开启 8. For update和Repeatable Read的区别 1.2 各种锁 …

STM32H750 CoreMark跑分测试

STM32H750 CoreMark跑分测试&#x1f50e;CoreMark跑分测试查询网站&#xff1a;https://www.eembc.org/coremark/scores.php&#x1f4dc; CoreMark源码&#xff1a;https://www.github.com/eembc/coremarkCoreMark移植和配置参考&#xff1a;https://community.st.com/t5/stm…

RabbitMQ如何确保消息发送和消息接收

消息发送确认 1 ConfirmCallback方法 ConfirmCallback 是一个回调接口&#xff0c;消息发送到 Broker 后触发回调&#xff0c;确认消息是否到达 Broker 服务器&#xff0c;也就是只 确认是否正确到达 Exchange 中。 2 ReturnCallback方法 通过实现 ReturnCallback 接口&#xf…

Linux:进程间通信-管道

Linux&#xff1a;进程间通信-管道 前言&#xff1a;为什么需要进程间通信&#xff1f; 你有没有想过&#xff0c;当你在电脑上同时打开浏览器、音乐播放器和文档时&#xff0c;这些程序是如何协同工作的&#xff1f;比如&#xff0c;浏览器下载的文件&#xff0c;为什么能被文…