【数据结构与算法】堆排序算法原理与实现:基于堆实现的高效排序算法

   

            💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《数据结构与算法》

                                  期待您的关注

 

1b7335aca73b41609b7f05d1d366f476.gif

 

目录

一、引言

堆排序的简介

堆排序的特点

二、堆的概念

三、堆排序算法的原理

四、堆排序的步骤

🍃构建堆 

🍃交换与调整

🍃重复过程

五、堆排序的性能分析

🍃时间复杂度:

🍃空间复杂度:

六、示例代码

七、总结


 

一、引言

堆排序的简介

堆排序(Heap Sort)是一种基于堆数据结构实现的排序算法。利用堆这种数据结构的高效性,通过构建和调整堆来实现排序,是一种性能优秀的排序算法。

堆排序的特点

  1. 时间复杂度:堆排序的最坏、最好、平均时间复杂度均为O(nlogn),其中n是待排序元素的数量。
  2. 稳定性:堆排序在排序过程中相等的元素不会交换位置,因此它是稳定的排序算法。
  3. 选择排序:堆排序是一种选择排序,它总是选择当前未排序部分的最大(或最小)元素进行排序。

 

二、堆的概念

关于堆的详细介绍,参考前置文章

【数据结构与算法】探索数组在堆数据结构中的妙用:从原理到实现-CSDN博客

 

三、堆排序算法的原理

  • 堆排序的基本思想是将待排序的序列构建成一个堆,然后依次将堆顶元素与堆尾元素交换,并将堆的大小减小1,再对剩余的堆进行调整,使其满足堆的性质。
  • 重复这个过程,直到堆的大小为1,此时排序完成。 

四、堆排序的步骤

🍃构建堆 

借助建堆算法,降序建小堆,升序建大堆,可以选择向上或者向下调整算法

向上调整建堆的原理:
模仿堆的插入操作来构建堆,从第一个子结点开始,将它看做是新插入的元素,向上调整至满足堆的性质,然后依次往后走,直到最后一个叶子节点完成上述调整

向下调整建堆的原理:
从最后一个父结点开始,先保证它和左右子树成为堆,然后依次往前走,保证每个父结点与左右子树成堆,直到最后根结点与左右子树成堆

 

关于建堆的向上调整算法和向下调整算法有时间复杂度推导

限于篇幅,这里就不展开推导了,直接给出结论

  • 向下调整建堆的时间复杂度为O(N)
  • 向上调整算法的时间复杂度为O(n*logN)

向下调整算法优于向上调整,所以应该选择向下调整算法

 这里分别给出向下调整建小堆和向下调整建大堆的算法

(如果关于向上调整算法和向下调整算法有疑惑,建议了解完堆的这篇文章之后再来看

【数据结构与算法】探索数组在堆数据结构中的妙用:从原理到实现-CSDN博客)

void Adjustdownsmall(DataType* a, int parent, int size)//向下调整建小堆算法
{int child = parent * 2 + 1;//先假定左孩子小while (child < size)//循环条件是未调整至叶子节点{if (child + 1 < size && a[child + 1] < a[child])//如果右孩子存在且更小,改变为右孩子child++;if (a[child] < a[parent])//如果子节点小于父节点,交换位置{Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}elsebreak;}
}
void Adjustdownbig(DataType* a, int parent, int size)//向下调整建大堆算法
{int child = parent * 2 + 1;//先假定左孩子大while (child < size)//循环条件是未调整至叶子节点{if (child + 1 < size && a[child + 1] > a[child])//如果右孩子存在且更大,改变为右孩子child++;if (a[child] > a[parent])//如果子节点大于父节点,交换位置{Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}elsebreak;}
}


 

🍃交换与调整

建堆之后,就是排序
以降序为例,每次将堆顶与堆尾数据交换(相当于将当前的最小值挪到最后),然后堆尾数据伪删除(有效数据个数--,不是真删除)进行一轮向下调整,恢复堆的结构

 

Swap(&a[0], &a[end]);//交换堆顶和堆尾数据
end--;
Adjustdownsmall(a, 0, end);//向下调整恢复堆的结构

 9b7e5cb844404312a312e019bae3b8bd.png

 

🍃重复过程

 重复上述交换与调整的过程,直到堆的大小为1,此时排序完成。

 

五、堆排序的性能分析

🍃时间复杂度:

  1. 建堆:对于长度为n的数组,建堆的时间复杂度为O(n)。这是因为建堆的过程中,元素需要逐个从数组尾部加入到堆中,并重新调整堆的结构以维持其性质。每个元素加入堆中最多会触发从该元素到根节点的路径上元素的重新调整,因此,平均而言,每个元素会触发O(log n)次调整。所以,建堆的总时间复杂度为O(n *log n)。但是,由于建堆的过程是线性的(从最后一个非叶子节点开始,逐个向上调整),所以实际的时间复杂度为O(n)。
  2. 排序:在排序阶段,每次从堆顶取出最大(或最小)元素,并重新调整堆结构的时间复杂度为O(log n)。因为需要排序n个元素,所以排序阶段的时间复杂度为O(n *log n)。

综上,堆排序的总时间复杂度为O(n) + O(n *log n) = O(n *log n)。

 

🍃空间复杂度:

堆排序的空间复杂度为O(1)。这是因为堆排序是原地排序算法,它只需要常数个额外的空间来存储临时变量,而不需要额外的存储空间来存储待排序的数组。所有的操作都是直接在原数组上进行的。所以,堆排序的空间复杂度非常低。

六、示例代码

(分别给出了完整的降序排序算法和升序排序算法)

#include<stdio.h>
#include<stdlib.h>#if 1
//堆排序
typedef int DataType;
void Swap(DataType* a, DataType* b)
{DataType tmp = *a;*a = *b;*b = tmp;
}
void Adjustdownsmall(DataType* a, int parent, int size)//向下调整建小堆算法
{int child = parent * 2 + 1;//先假定左孩子小while (child < size)//循环条件是未调整至叶子节点{if (child + 1 < size && a[child + 1] < a[child])//如果右孩子存在且更小,改变为右孩子child++;if (a[child] < a[parent])//如果子节点小于父节点,交换位置{Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}elsebreak;}
}
void Adjustdownbig(DataType* a, int parent, int size)//向下调整建大堆算法
{int child = parent * 2 + 1;//先假定左孩子大while (child < size)//循环条件是未调整至叶子节点{if (child + 1 < size && a[child + 1] > a[child])//如果右孩子存在且更大,改变为右孩子child++;if (a[child] > a[parent])//如果子节点大于父节点,交换位置{Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}elsebreak;}
}void HeapSortDOrder(DataType* a,int size)//降序排序
{//向下调整建小堆for (int i = (size - 2) / 2; i >= 0; i--)//从最后一个父节点调整{Adjustdownsmall(a, i, size);}int end = size - 1;while (end>0){Swap(&a[0], &a[end]);//交换堆顶和堆尾数据end--;Adjustdownsmall(a, 0, end);//向下调整恢复堆的结构}
}
void HeapSortAOrder(DataType* a, int size)//升序排序
{//向下调整建大堆for (int i = (size - 2) / 2; i >= 0; i--)//从最后一个父节点调整{Adjustdownbig(a, i, size);}int end = size - 1;while (end > 0){Swap(&a[0], &a[end]);//交换堆顶和堆尾数据end--;Adjustdownbig(a, 0, end);//向下调整恢复堆的结构}
}
#endifvoid print(DataType* a, int size)
{for (int i = 0; i < size; i++){printf("%d ", a[i]);}printf("\n");
}
int main()
{int arr[] = { 12,3,5,78,46,15,23,19,20,36,52 };int size = sizeof(arr) / sizeof(arr[0]);HeapSortDOrder(arr, size);//降序print(arr, size);HeapSortAOrder(arr, size);//升序print(arr, size);}

 27c88bb4be3d4b43beb24363a0fd02ae.png

七、总结

堆排序算法是一种高效且实用的排序算法,它通过利用堆数据结构的特点和性质,实现了对数据的高效排序。在实际应用中,我们可以根据问题的特点选择使用堆排序算法,以提高程序的性能和效率。

 

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

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

相关文章

软件测试面试1000问(含答案)

1、自动化代码中,用到了哪些设计模式? 单例设计模式工厂模式PO设计模式数据驱动模式面向接口编程设计模式 2、什么是断言( Assert) ? 断言Assert用于在代码中验证实际结果是不是符合预期结果&#xff0c;如果测试用例执行失败会抛出异常并提供断言日志 3、什么是web自动化…

数据结构预科

在堆区申请两个长度为32的空间&#xff0c;实现两个字符串的比较【非库函数实现】 要求&#xff1a; 1> 定义函数&#xff0c;在对区申请空间&#xff0c;两个申请&#xff0c;主函数需要调用2次 2> 定义函数&#xff0c;实现字符串的输入&#xff0c;void input(char …

Jenkins容器的部署

本文主要是记录如何在Centos7上安装docker,以及在docker里面配置tomcat、mysql、jenkins等环境。 一、安装docker 1.1 准备工作 centos7、VMware17Pro 1.2 通过yum在线安装dokcer yum -y install docker1.3 启动docker服务 systemctl start docker.service1.4 查看docke…

Java传引用问题

本文将介绍 Java 中的引用传递&#xff0c;包括其定义、实现方式、通过引用修改原来指向的内容和通过引用修改当前引用的指向的区别 目录 1、引用传递的概念 2、引用传递的实现方式 3、传引用会发生的两种情况&#xff1a; 通过引用修改当前引用的指向 通过引用修改原来指…

《数据仓库与数据挖掘》 总复习

试卷组成 第一章图 第二章图 第三章图 第四章图 第五章图 第六章图 第九章图 第一章 DW与DM概述 &#xff08;特点、特性&#xff09; DB到DW 主要特征 &#xff08;1&#xff09;数据太多&#xff0c;信息贫乏&#xff08;Data Rich&#xff0c; Information Poor)。 &a…

H2 Database Console未授权访问漏洞封堵

背景 H2 Database Console未授权访问&#xff0c;默认情况下自动创建不存在的数据库&#xff0c;从而导致未授权访问。各种未授权访问的教程&#xff0c;但是它怎么封堵呢&#xff1f; -ifExists 很简单&#xff0c;启动参数添加 -ifExists &#xff0c;它的含义&#xff1a…

【机器学习】机器学习的重要方法——线性回归算法深度探索与未来展望

欢迎来到 破晓的历程博客 引言 在数据科学日益重要的今天&#xff0c;线性回归算法以其简单、直观和强大的预测能力&#xff0c;成为了众多领域中的基础工具。本文将详细介绍线性回归的基本概念、核心算法&#xff0c;并通过五个具体的使用示例来展示其应用&#xff0c;同时探…

CASS7.0按方向和距离绘制图形

1、绘制工具 2、按方向和距离绘制 &#xff08;1&#xff09;切换方向 &#xff08;2&#xff09;距离输入

Python函数缺省参数的 “ 坑 ” (与C++对比学习)

我们都知道Python函数的缺省参数可以降低我们调用函数的成本&#xff0c;但是一般我们的缺省参数都是不可变对象&#xff0c;如果是可变对象&#xff0c;我们对其多次调用会发生什么呢&#xff1f; def func(arr[]):arr.append(Hello)print(arr)func() func() func() 这貌似…

MongoDB-社区版-本地安装

系统&#xff1a;win10 1. 下载server:Download MongoDB Community Server | MongoDB 我选的zip包 2. 下载shell&#xff1a;MongoDB Shell Download | MongoDB 我选的zip包 3. 启动server 4. 启动shell, 完成

MYSQL函数进阶详解:案例解析(第19天)

系列文章目录 一、MySQL的函数&#xff08;重点&#xff09; 二、MySQL的窗口函数&#xff08;重点&#xff09; 三、MySQL的视图&#xff08;熟悉&#xff09; 四、MySQL的事务&#xff08;熟悉&#xff09; 文章目录 系列文章目录前言一、MySQL的函数1. 聚合函数2. group_c…

Redis 多数据源自定义配置 Spring Boot 升级版

文章目录 1.前言2.git 示例地址3.需求4.代码实现4.1 application.properties 配置文件4.2 获取 application.properties 中的 redis 配置4.2.1 Environment 对象来获取自定义 redis 配置 4.3 初始化 RedisTemplate 对象&#xff0c;并注册到 Spring IOC 容器4.3.1 初始化方法4.…

spring boot (shiro)+ websocket测试连接不上的简单检测处理

1、用前端连接测试的demo一切正常&#xff0c;但是到了项目中连接不上了 一开始以为是地址错&#xff0c;但是换了apifox测试也是不可以。 2、考虑是shiro进行了拦截了&#xff0c;所以就访问不到了地址&#xff0c;那么就放行。 3、再次用apifox测试&#xff0c;成功了。 当然…

马拉松报名小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;赛事信息管理&#xff0c;赛事报名管理&#xff0c;活动商城管理&#xff0c;留言板管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;赛事信息&…

C++:智能指针

目录 前言 1.内存泄漏及其危害 2.内存泄漏分类&#xff1a; 3.如何检测内存泄漏 4.如何避免内存泄漏 一、为什么需要智能指针&#xff1f; 二、智能指针的使用及其原理 1.RAII 2.智能指针 3.std::auto_ptr 4.std::unique_ptr 5.std::shared_ptr 6.std::weak_ptr…

SA 注册流程

目录 1. UE开机后按照3GPP TS 38.104定义的Synchronization Raster搜索特定频点 2.UE尝试检测PSS/SSS&#xff0c;取得下行时钟同步&#xff0c;并获取小区的PCI&#xff1b;如果失败则转步骤1搜索下一个频点&#xff1b;否则继续后续步骤&#xff1b; 3.解析Mib&#xff0c;…

从0到1构建渠道运营体系:实战案例与策略指南

引言 在当今竞争激烈的市场环境中&#xff0c;有效的渠道运营是企业实现产品或服务快速触达目标用户、提升市场份额的关键。从零开始构建一个高效的渠道运营体系&#xff0c;不仅需要深思熟虑的策略规划&#xff0c;还需要灵活应变的实战操作。本文将结合实战案例&#xff0c;…

JDK新特性之协程

在 JVM 中&#xff0c;java 线程直接映射内核线程&#xff0c;因此 java 线程的创建、销毁和调度都要依赖内核态的操作&#xff08;系统调用&#xff09;。而协程是真正的用户线程&#xff0c;如上图所示很多的协程可以映射很少的几个内核线程&#xff0c;并且协程的创建、销毁…

gitee代码初次上传步骤

ps. 前提是已经下载安装gitee 一、在本地项目目录下空白处右击&#xff0c;选择“Git Bash Here” 二、初始化 git init 三、添加、提交代码&#xff08;注意add与点之间的空格&#xff09; git add . git commit -m 添加注释 四、连接、推送到gitee仓库 git remote add …

Renderless 思想正在影响前端开发

本文由前端小伙伴方长_beezen 原创。欢迎大家踊跃投稿。 原文链接&#xff1a;https://juejin.cn/post/7385752495535472655 前言 截止到 2024 年&#xff0c;跨端应用开发所需要考虑的兼容性&#xff0c;已经涵盖了框架、平台和设备类型等多个方面&#xff0c;例如&#xff1…