堆排序实战:轻松实现高效排序,附详细Java代码

简介: 嗨,大家好!我是小米,一名热爱技术分享的程序员。今天要带大家了解堆排序——一种基于二叉堆的数据结构,具有O(n log n)时间复杂度的选择排序算法。堆排序分为构建大顶堆和排序两个阶段:先建堆使根节点为最大值,再通过交换根节点与末尾节点并调整堆来逐步排序。它稳定高效,空间复杂度仅O(1),适合对稳定性要求高的场合。虽然不如快速排序快,但在避免递归和节省空间方面有优势。一起动手实现吧!如果有任何疑问,欢迎留言交流!



Hello,大家好!我是你们的小米,今天又来给大家分享干货啦!最近很多小伙伴们都对排序算法产生了浓厚的兴趣,继上次分享了“手写快排”之后,今天我们再来搞搞堆排(Heap Sort),带大家一起动手手写堆排算法吧!

什么是堆排序?

堆排序是一种基于二叉堆(Binary Heap)这种数据结构的排序算法,属于选择排序的一种。堆排序的时间复杂度为 O(n log n),在最坏的情况下依然表现稳定。和快速排序相比,它没有快排那样的递归深度问题,因此适合用在对稳定性要求高且空间不允许递归的场景下。

堆排序主要有两个步骤:

  • 构建大顶堆(或小顶堆):根据数组构建出一个大顶堆(父节点的值大于子节点),这样堆顶元素就是最大值。
  • 交换堆顶元素与末尾元素并调整堆:将堆顶元素(最大值)与末尾元素交换,缩小堆的范围,重新调整堆。循环此过程,直到整个数组有序。

堆排序的原理

我们以大顶堆为例来讲解堆排序的核心原理。首先,我们将数组看成一棵完全二叉树,根节点为最大元素。通过堆调整(Heapify)操作,保持堆的结构特性。之后,我们将堆顶元素与最后一个元素交换,再对剩余元素重新调整成堆,最终完成排序。

堆排序的步骤

  • 构建堆:将无序数组调整成大顶堆。
  • 排序:依次将堆顶元素与末尾元素交换,缩小堆的范围,并重新调整堆。

手写堆排序代码

好啦,理论讲完了,接下来进入我们的实战环节。我们使用Java来手写一个堆排序算法吧!

代码讲解

上面的代码实现了堆排序的核心步骤。下面我来一步步讲解:

  • 构建初始大顶堆:我们从数组的中间位置开始向前遍历(for (int i = n / 2 - 1; i >= 0; i--)),因为数组的后一半是叶子节点,不需要调整堆。通过调用 heapify 函数,将每一个非叶子节点调整成大顶堆。

  • 堆的调整(heapify)heapify 函数用于调整堆的结构。它接收三个参数:数组 arr、数组的长度 n 和当前节点的索引 i。该函数会比较当前节点、左子节点和右子节点的大小,确保父节点是最大值。如果发现子节点比父节点大,则交换节点,然后递归调用 heapify 函数对交换后的子树继续调整。

  • 排序:构建好大顶堆后,开始排序。在每次循环中,将堆顶元素(最大值)与数组的最后一个元素交换,然后将剩余的元素重新调整为大顶堆。这个过程会一直进行到堆的范围缩小到只剩一个元素为止,整个数组最终有序。

堆排序的时间复杂度

堆排序的时间复杂度是 O(n log n),这里的 n 是数组的大小。构建大顶堆的时间复杂度为 O(n),每次调整堆的时间复杂度为 O(log n),总共需要调整 n-1 次,所以总的时间复杂度为 O(n log n)

堆排序的空间复杂度

堆排序是原地排序算法,也就是说它只需要常数个额外的空间,空间复杂度为 O(1)。这一点相较于快排更为优越,快排在最坏情况下的空间复杂度可能会达到 O(n)(因为递归深度过深)。

堆排序的优缺点

优点:

  • 时间复杂度稳定:不管输入数组的状态如何,堆排序的时间复杂度总是 O(n log n)
  • 空间复杂度低:堆排序是原地排序,空间复杂度为 O(1),比快排更节省空间。

缺点:

  • 不稳定排序:堆排序是不稳定排序,相同元素的相对顺序可能会被改变。
  • 不如快排快:尽管堆排序的时间复杂度和快排相同,但是堆排序的常数系数较大,实际运行速度往往比不上快排。

END

堆排序是一种比较实用的排序算法,特别适用于对稳定性要求高、递归深度限制较大、以及空间资源有限的场景。尽管它的实际表现可能不如快排,但它在一些特定情况下仍然是非常有价值的工具。

如果你是算法爱好者,推荐大家动手写一写堆排序,深入理解算法的原理!如果你有什么不懂的地方,欢迎在评论区留言哦,我会积极回复的!今天的分享就到这里啦,我们下次再见!

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号软件求生,获取更多技术干货!

相关文章
|
4天前
|
Java 程序员
Java 排序神器:Comparable 和 Comparator 该怎么选?
嗨,大家好,我是小米!今天和大家聊一聊Java社招面试中常考的经典问题——Comparable和Comparator的区别。Comparable定义对象的自然排序,适用于单一固定的排序规则;Comparator则是策略接口,用于定义自定义排序规则,适用于多样化或多变的排序需求。掌握这两者的区别是理解Java排序机制的基础,也是面试中的加分题。结合实际项目场景深入探讨它们的应用,能更好地打动面试官。如果你觉得有帮助,欢迎点赞、收藏、分享,期待你的一键三连!我们下期见~ 我是小米,一个喜欢分享技术的程序员,关注我的微信公众号“软件求生”,获取更多技术干货!
37 20
|
2天前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
20天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
42 3
|
24天前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
27天前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
62 2
|
1月前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
85 5
|
1月前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
45 1
|
1月前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
5月前
|
搜索推荐 算法 Java
|
5月前
|
搜索推荐 算法 Java
经典排序算法之-----选择排序(Java实现)
这篇文章通过Java代码示例详细解释了选择排序算法的实现过程,包括算法的基本思想、核心代码、辅助函数以及测试结果,展示了如何通过选择排序对数组进行升序排列。
经典排序算法之-----选择排序(Java实现)