堆排序实战:轻松实现高效排序,附详细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岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号软件求生,获取更多技术干货!

相关文章
|
1天前
|
SQL JavaScript 前端开发
基于Java访问Hive的JUnit5测试代码实现
根据《用Java、Python来开发Hive应用》一文,建立了使用Java、来开发Hive应用的方法,产生的代码如下
15 6
|
7天前
|
存储 Java 开发者
【Java新纪元启航】JDK 22:解锁未命名变量与模式,让代码更简洁,思维更自由!
【9月更文挑战第7天】JDK 22带来的未命名变量与模式匹配的结合,是Java编程语言发展历程中的一个重要里程碑。它不仅简化了代码,提高了开发效率,更重要的是,它激发了我们对Java编程的新思考,让我们有机会以更加自由、更加创造性的方式解决问题。随着Java生态系统的不断演进,我们有理由相信,未来的Java将更加灵活、更加强大,为开发者们提供更加广阔的舞台。让我们携手并进,共同迎接Java新纪元的到来!
33 11
|
1天前
|
存储 负载均衡 Java
Jetty技术深度解析及其在Java中的实战应用
【9月更文挑战第3天】Jetty,作为一款开源的、轻量级、高性能的Java Web服务器和Servlet容器,自1995年问世以来,凭借其卓越的性能、灵活的配置和丰富的扩展功能,在Java Web应用开发中占据了举足轻重的地位。本文将详细介绍Jetty的背景、核心功能点以及在Java中的实战应用,帮助开发者更好地理解和利用Jetty构建高效、可靠的Web服务。
11 2
|
5天前
|
并行计算 Java 开发者
探索Java中的Lambda表达式:简化代码,提升效率
Lambda表达式在Java 8中引入,旨在简化集合操作和并行计算。本文将通过浅显易懂的语言,带你了解Lambda表达式的基本概念、语法结构,并通过实例展示如何在Java项目中应用Lambda表达式来优化代码,提高开发效率。我们将一起探讨这一现代编程工具如何改变我们的Java编码方式,并思考它对程序设计哲学的影响。
|
5天前
|
安全 Java 测试技术
掌握Java的并发编程:解锁高效代码的秘密
在Java的世界里,并发编程就像是一场精妙的舞蹈,需要精准的步伐和和谐的节奏。本文将带你走进Java并发的世界,从基础概念到高级技巧,一步步揭示如何编写高效、稳定的并发代码。让我们一起探索线程池的奥秘、同步机制的智慧,以及避免常见陷阱的策略。
|
7天前
|
Java 开发者
Java中的多线程编程基础与实战
【9月更文挑战第6天】本文将通过深入浅出的方式,带领读者了解并掌握Java中的多线程编程。我们将从基础概念出发,逐步深入到代码实践,最后探讨多线程在实际应用中的优势和注意事项。无论你是初学者还是有一定经验的开发者,这篇文章都能让你对Java多线程有更全面的认识。
14 1
|
12天前
|
数据采集 存储 前端开发
Java爬虫开发:Jsoup库在图片URL提取中的实战应用
Java爬虫开发:Jsoup库在图片URL提取中的实战应用
|
11天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
73 6
【Java学习】多线程&JUC万字超详解
|
4天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。