系列文章目录

 第一篇:【排序算法】①直接插入排序-CSDN博客

第二篇:【排序算法】②希尔排序-CSDN博客

第三篇:【排序算法】③直接选择排序-CSDN博客

第四篇:【排序算法】④堆排序-CSDN博客

第五篇:【排序算法】⑤冒泡排序-CSDN博客

第六篇:【排序算法】⑥快速排序:Hoare、挖坑法、前后指针法-CSDN博客

第七篇:【排序算法】⑦归并排序-CSDN博客


目录

系列文章目录

前言

一、堆是什么?

二、实现堆排序

1.堆排序所需要的堆函数

HeapCreate:堆的构建

核心算法BigADjustUp:(大堆)向上调整算法

HeapPopBig:删除节点

核心算法BigADjustDown:向下调整算法

HeapDestory:堆的销毁

2.堆排序代码

三、分析堆排序

总结



前言

堆排序是指利用二叉树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。

值得注意的是:排升序要建大堆,排降序建小堆。


一、堆是什么?

想要了解堆是什么,首先需要明白什么是完全二叉树

完全二叉树:

1. 二叉树:由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

2. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。
3. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

堆的定义:

如果有一个码的集合K = {k0 ,k1 ,k2 ,…,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:ki <= k2i+1且ki <= k2i+2( ki>=k2i+1 且ki >=k2i+2 ) i = 0,1,2…,则称为小堆(或大堆).

简单来说:

完全二叉树由于其性质非常适合使用顺序结构存储,所以,现实中我们通常把堆使用顺序结构的数组来存储。

于是我们可以推出堆的数据结构:

typedef int HPDataType;typedef struct Heap
{HPDataType* _a;int _size;int _capacity;
}Heap;

上面是简述堆的概念,目的是为堆排序准备。若是之前未接触过堆这种数据结构的读者,强烈建议先看看链接文章:【数据结构】二叉树①-堆-CSDN博客


二、实现堆排序

本文以外部建堆实现堆排升序为例进行介绍,至于本地建堆或者堆排降序由于核心思想未变,可同理得出。

1.堆排序所需要的堆函数


HeapCreate:堆的构建

通过传入数组a和数据个数nums,直接将传入的数组构造成小堆。

// 堆的初始化,并构建传入数组:为堆排序
void HeapCreateBig(Heap* hp, HPDataType* a, int nums)
{if(!hp||!a)return;HPDataType* temp = (HPDataType*)malloc(sizeof(HPDataType) * nums);if (temp){hp->_a = temp;hp->_capacity = nums;hp->_size = nums;}else{perror("HeapCreate malloc fail");exit(-1);}for (int i = 0; i < nums; ++i){hp->_a[i] = a[i];BigADjustUp(hp, i);}
}

上述代码的核心在于最后的for循环:

①在循环中,将外部a数组的数据一个一个赋予给malloc出来堆中的数组;

②每赋予一个值,便调用依次BigADjustUp函数。

核心算法BigADjustUp:(大堆)向上调整算法


假设有一个数组,在逻辑上看做一颗完全二叉树。我们通过从根节点开始的向上调整算法可以把它调整成一个大堆。

假设该数组为:

int a[] = { 17,15,28,22,13,5,9 };

可以看到该数组中的内容并不符合大堆要求。

向上调整算法核心思路:

①默认数组第一个元素为根节点数据;

②之后每Push进一个数据,便与它的父节点比较,若父节点数据小于子节点数据,则交换,然后将父节点(下标)赋值给子节点,再计算新父节点,重复上述步骤,直至子节点到根结点处(下标为0时)。

③中途若父节点数据大于等于子节点数据(满足小堆要求),则break跳出循环。

代码参考:

//大堆调整:
void BigADjustUp(Heap* hp, int loc)
{assert(hp);HPDataType* a = hp->_a;int child = loc;int parent = (child - 1) / 2;while (child > 0){if (a[parent] < a[child]){swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}

解释:

父节点(parent)、子节点(child)在代码中表示的是数组下标;

loc,即location位置的意思,记录Push数据在数组中的下标;

计算新父节点:(左孩子或者右孩子的下标-1)/ 2就==他们父节点的下标,比如数组下标1与数组下标2的父节点在数组中就是下标0;

④每push进一个数据,先让他做当前位置对应的左或者右孩子(该位置肯定是是叶节点),然后通过向上调整算法,若数据大于父节点中的数据,则交换、上升。

用该算法调整后:

int a[] = { 17,15,28,22,13,5,9 };

调整后:{28,22,17,15,13,5 ,9};


HeapPopBig:删除节点

将堆顶元素与数组最后一个元素交换,_size--,然后调用向下调整算法。

// 堆的删除:为堆排序
void HeapPopBig(Heap* hp)
{assert(hp);assert(hp->_a);swap(&hp->_a[0], &hp->_a[hp->_size - 1]);hp->_size--;BigADjustDown(hp, 0);
}

将堆顶元素与最后一个元素交换位置后,重要的是调用BigADjustDown()函数,使得堆能继续成立。

核心算法BigADjustDown:向下调整算法

向下调整算法有一个前提:左右子树必须是堆,才能调整。

堆的删除,即删除堆顶元素需要用到第二个核心算法——向下调整算法。

我们先说明堆删除的思想:将堆顶元素与数组最后一个元素交换,然后_size--。

也就是说,向下调整算法的核心目标是:将堆顶所在的元素调整到适合它的位置。

算法思路:

选择该父节点对应的左/右孩子节中值较大的那一个,与父节点的值交换(一开始是根节点);

②将交换的孩子节点当作新一轮的父节点,重复①;

③直到数组末尾,或者中途遇到小于或等于,满足小堆条件退出。

void BigADjustDown(Heap* hp, int loc)
{assert(hp);HPDataType* a = hp->_a;//parent与child均为下标int parent = loc;int lchild = parent * 2 + 1, rchild = (parent + 1) * 2, max_child = 0;while (parent < hp->_size){max_child = a[lchild] > a[rchild] ? lchild : rchild;if (hp->_a[max_child] > hp->_a[parent] && max_child < hp->_size){swap(&hp->_a[parent], &hp->_a[max_child]);parent = max_child;lchild = parent * 2 + 1;rchild = (parent + 1) * 2;}else{break;}}}

解释:

①新父节点下标:已知父节点的数组下标,那么该节点的左孩子数组下标=父节点下标*2+1,右孩子数组下标=(父节点下标+1)*2。如父节点数组下标为0,则左孩子为0*2+1,右孩子为(0+1)*2。

②BigADjustDown是配合HeapPopBig使用的,所以不要忘了实际场景是:堆顶与末尾数据进行了交换,需要BigADjustDown重新调整堆。

这也是为什么排升序要建大堆,排降序建小堆的原因:因为实现机制是将堆顶数据倒插到数组尾,大堆堆顶是数组数据最大的,故为升序;小堆堆顶是数组最小的,故为降序。


HeapDestory:堆的销毁

记得将无用指针置空,防止野指针。

// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);free(hp->_a);hp->_capacity = hp->_size = 0;hp->_a = NULL;
}


2.堆排序代码

//堆排序:升序
void HeapSortUp(int* a, int n)
{if (!a)return;Heap he;HeapCreateBig(&he, a, n);int cnt = n;while (cnt){HeapPopBig(&he);cnt--;}for (int i = 0; i < n; ++i)a[i] = he._a[i];HeapDestory(&he);
}

解释:

①将传入的数组通过HeapCreateBig构建起大堆;

②while循环,通过利用HeapPopBig每循环一次将堆顶(最大数据)移动至数组末尾,由于HeapPopBig一次成员变量_size便会--(数组尾下标不断向前),当循环结束后成员变量数组_a存储的数据便是升序排列;

这里利用HeapPopBig删除机制,将堆顶逐渐移动至数组尾,但注意不是真正的删除了而是堆中的_size--了(下次再向堆中push数据就会将原数据覆盖),实际如果不再push那么这些数据是依然存在的。

③最后的for循环将排好的堆内数组挨个赋值给外部数组,之后再销毁堆。

 以上述调整后:{28,22,17,15,13,5 ,9}的数组为例模仿代码执行,

while中HeapPopBig执行情况:

三、分析堆排序

特性总结:
1.同样是选择排序,相比直接选择排序,堆排序使用堆来选数,效率高了很多。
2. 时间复杂度:建堆过程为O(n),每次调整(弹出)为O(log n),总共n次调整,所以整体时间复杂度为O(n log n)
3. 空间复杂度:本文中的代码实现为O(N)(也可以采取另一种原地建堆的方法,那样空间复杂度为O(1));
4. 稳定性:不稳定,交换堆顶和末尾元素时,可能破坏相同元素的相对顺序。


总结

本文是【排序算法】系类的第四篇,主要介绍了什么是堆,以及如何实现堆,最后分析了堆的特性。

整理不易,希望对你有所帮助。

读完点赞,手留余香~

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

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

相关文章

Android领域驱动设计与分层架构实践

引言在Android应用开发中&#xff0c;随着业务逻辑日益复杂&#xff0c;传统的MVC或简单MVP架构往往难以应对。领域驱动设计(Domain-Driven Design, DDD)结合分层架构&#xff0c;为我们提供了一种更系统化的解决方案。本文将探讨如何在Android项目中应用DDD原则与分层架构&…

Android12 Framework电话功能UI定制

文章目录简介代码中间按钮Fragment创建VideoCallFragmentFragment管理添加按键挂断电话功能相关文章简介 Android版本&#xff1a;12 芯片平台&#xff1a;展锐 如下图为通话中的UI&#xff0c;打电话出去时显示的UI与此也差不多&#xff0c;但来电时UI是不一样的 这个界面是…

高并发场景下分布式ID生成方案对比与实践指南

高并发场景下分布式ID生成方案对比与实践指南 在分布式系统中&#xff0c;唯一且全局有序的ID生成器是很多业务的底层组件。随着系统并发量不断攀升&#xff0c;如何在高并发场景下保证ID的唯一性、性能、可用性和可扩展性&#xff0c;成为后端架构师需要重点考虑的问题。本文将…

Emscripten 指南:概念与使用

Emscripten 指南&#xff1a;概念与使用 什么是 Emscripten&#xff1f; Emscripten 是一个开源的编译器工具链&#xff0c;用于将 C/C 代码编译成高效的 WebAssembly&#xff08;Wasm&#xff09;和 JavaScript。它基于 LLVM 编译器架构&#xff0c;允许开发者&#xff1a; ✅…

使用镜像网站 打开克隆 GitHub 网站仓库内容 git clone https://github.com/

GitHub 网站有时因 DNS 解析问题或网络限制&#xff0c;国内访问可能会受限。使用镜像网站打开网站 使用镜像网站&#xff1a;GitHub 有一些镜像网站&#xff0c;可替代官网访问&#xff0c;如https://hub.fastgit.org、https://gitclone.com、https://github.com.cnpmjs.org等…

Linux随记(二十二)

一、redhat6.5 从openssh5.3 升级到openssh10 - 报错处理【升级后账号密码一直错误 和 sshd dead but subsys locked】 虚拟机测试情况 - 正常&#xff1a;情况一、 升级后账号密码一直错误 情况二、 执行service sshd status出现 sshd dead but subsys locked

机器学习之TF-IDF文本关键词提取

目录 一、什么是 TF-IDF&#xff1f; 1.语料库概念理解 二、TF-IDF 的计算公式 1. 词频&#xff08;TF&#xff09; 2. 逆文档频率&#xff08;IDF&#xff09; 3. TF-IDF 值 三、关键词提取之中文分词的实现 四、TF-IDF简单案例实现 &#xff08;1&#xff09;数据集…

Flutter屏幕和字体适配(ScreenUtil)

一、简介 flutter_screenutil 是一个 Flutter 插件&#xff0c;专门用于处理屏幕适配问题。它简化了不同设备间尺寸差异的处理&#xff0c;确保你的应用在各种屏幕上都能保持良好的显示效果。开发者可以通过简单的调用来设置基于设计图尺寸的控件宽高和字体大小。 项目地址&a…

mimiconda+vscode

安装miniconda实现python包管理&#xff0c;并通过vscode进行编写python代码 miniconda简单介绍 Miniconda 是 Anaconda 公司的一个轻量级 Python 发行版本&#xff0c;它包含了最基本的包管理器 conda 和 Python 环境&#xff0c;只带最核心的组件&#xff0c;没有额外的大量科…

Windows文件时间修改指南:从手动到自动化

修改文件的时间属性可以满足多种需求。比如&#xff0c;它可以帮助整理文件&#xff0c;使得文件按照特定的时间顺序排列&#xff0c;有助于更好地管理资料。它的体积真小&#xff0c;才300多KB。能用来调整文件的创建时间、最后访问和修改时间。文件时间属性修改_NewFileTime.…

能刷java题的网站

以下是一些适合刷Java题的优质网站&#xff0c;涵盖从基础到进阶、算法面试及实战项目等多种需求&#xff1a; ​一、综合编程练习平台​ ​LeetCode​&#xff08;leetcode.com&#xff09; ​特点​&#xff1a;全球最知名的算法题库&#xff0c;含海量Java题目&#xff0c;分…

掘金数据富矿,永洪科技为山东黄金定制“数智掘金”实战营

在黄金开采的轰鸣声中&#xff0c;另一场静水深流的“掘金行动”正悄然展开。山东黄金集团&#xff0c;这个行业的巨头&#xff0c;在深挖地层宝藏的同时&#xff0c;也敏锐捕捉到数据洪流中蕴藏的价值富矿。然而&#xff0c;当海量业务数据汇聚&#xff0c;如何从中精准提炼决…

【论文阅读】BEVFormer论文解析及Temporal Self-Attention、Spatial Cross-Attention注意力机制详解及代码示例

BEVFormer: Learning Bird’s-Eye-ViewRepresentation from Multi-Camera Images via Spatiotemporal Transformers|Temporal Self-Attention、Spatial Cross-Attention注意力机制详解 BEVFormer&#xff08;Bird’s-Eye-View Former&#xff09;是一种先进的计算机视觉模型&am…

在 Ubuntu 中docker容器化操作来使用新建的 glibc-2.32

在 Ubuntu 中使用容器化操作来使用新建的 glibc-2.32,可以通过创建自定义 Docker 镜像来实现。以下是完整的解决方案: 方案 1:创建包含 glibc-2.32 的 Docker 镜像 1. 创建 Dockerfile dockerfile # 使用 Ubuntu 基础镜像 FROM ubuntu:20.04# 安装编译依赖 RUN apt-get …

GOOUUU ESP32-S3-CAM 果云科技开发板开发指南(二)(超详细!)Vscode+espidf 摄像头拍摄视频实时传输到LCD,文末附源码

书接上回&#xff0c;上一篇blog是使用esp32s3通过ov2640摄像头拍摄到一帧照片&#xff0c;并把它保存到了SD卡中&#xff0c;这第二篇就通过LCD将拍摄到的图片显示到LCD上&#xff0c;本次分享硬件使用的 ESP32-S3-CAM 果云科技开发板&#xff0c;并且使用了配套的LCD扩展板&a…

攻防世界-ics-05(远程文件执行)

一.审题大致浏览一下网页&#xff0c;发现就这边会有东西。看一下源码会不会有东西或者稍微点击一下这个页面的内容看会不会出现东西。点击了一下这个云平台设备维护中心发现url变了&#xff0c;是get的方法传page参数二.尝试漏洞类型自己这边试了sql注入发现不是&#xff0c;试…

Dell PowerEdge: Servers by generation (按代系划分的服务器)

Dell PowerEdge: Servers by generation {按代系划分的服务器}1. Table of 17th, 16th, 15th, and 14th Generation PowerEdge servers2. List of all PowerEdge server models including Type, CPU vendor, Generation, and Remote ManagementReferencesPowerEdge: Servers by…

Rust学习笔记(二)|变量、函数与控制流

本篇文章包含的内容1 变量与常量2 类型2.1 标量类型2.2 复合类型3 函数4 控制流4.1 分支4.2 循环1 变量与常量 在Rust中&#xff0c;使用let关键字声明一个变量&#xff0c;变量默认是不可变的。如果要声明可变变量&#xff0c;需要使用mut关键字将其声明为可变变量。 let x …

【渲染流水线】[几何阶段]-[图元装配]以UnityURP为例

【从UnityURP开始探索游戏渲染】专栏-直达 前情提要 【渲染流水线】主线索引-从数据到图像以UnityURP为例-CSDN博客 图元装配负责将离散顶点组装成完整几何图元&#xff08;如点、线、三角形、三角形条带&#xff09; &#xff08;对渲染的探索是个持续不断完善的过程&#x…

jvm有哪些垃圾回收器,实际中如何选择?

7.G1收集器一款面向服务端应用的垃圾收集器。 特点如下&#xff1a; 并行与并发&#xff1a;G1能充分利用多CPU、多核环境下的硬 件优势&#xff0c;使用多个CPU来缩短Stop-The-World停顿时间。部分收集器原本需要停顿Java线程来执行GC动作&#xff0c;G1收 集器仍然可以通过并…