作为一名对技术充满热情的学习者,我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代,我远非专家,而是一位不断追求进步的旅行者。通过这篇博客,我想分享我在某个领域的学习经验,与大家共同探讨、共同成长。请大家以开放的心态阅读,相信你们也会在这段知识之旅中找到启示。
前言
继续继续继续,今天我们还是排序算法,认识新的排序算法----堆排序
一、什么是堆排序
堆排序是一种基于比较的排序算法,它利用二叉堆这种数据结构的特性来对元素进行排序。二叉堆是一个近似完全二叉树的数据结构,它分为两种类型:
- 最大堆:任何一个父节点的值都大于或等于它的孩子节点的值。
- 最小堆:任何一个父节点的值都小于或等于它的孩子节点的值。
在堆排序算法中,最大堆用于升序排序,而最小堆则用于降序排序。下面是堆排序算法的基本步骤:
-
建立堆:将待排序的数组构造成一个最大堆(升序排序)。这个步骤是将数组转换成最大堆的过程,通常可以从最后一个非叶子节点开始向上进行调整。
-
堆排序:
- 从堆中逐个取出元素进行排序;
- 每一次将根节点(即当前最大值)与堆中最后一个元素交换,并对剩余的堆结构进行调整,重建最大堆;
- 这个过程一直重复,直到堆中仅剩下一个元素,排序完成。
堆排序过程的伪代码如下:
function heapSort(arr):
n = length(arr)
# 构建最大堆
for i from n/2 - 1 down to 0:
heapify(arr, n, i)
# 一个一个地从堆顶取出元素
for i from n - 1 down to 0:
# 将当前根节点,也就是最大值移动到数组末尾
swap arr[0] with arr[i]
# 在减小堆的大小后,恢复堆的性质
heapify(arr, i, 0)
# 将一个数组转换成最大堆
function heapify(arr, n, i):
largest = i # 初始化最大为根节点
l = 2 * i + 1 # 左子节点
r = 2 * i + 2 # 右子节点
# 如果左子节点大于根节点
if l < n and arr[l] > arr[largest]:
largest = l
# 如果右子节点大于最大目前节点
if r < n and arr[r] > arr[largest]:
largest = r
# 如果最大值不是根节点,则进行交换
if largest != i:
swap arr[i] with arr[largest]
# 递归地定义子堆为最大堆
heapify(arr, n, largest)
堆排序的时间复杂度是O(nlogn)
,其中n
是数组的长度。这是因为创建最大堆的过程是O(n)
,而从最大堆中取出每个元素进行排序的过程是O(logn)
的(因为需要进行堆调整),这样的操作需要进行n
次。堆排序是一个不稳定的排序算法,但是它的优势在于不需要额外的存储空间(原地排序)。
二、练习
用堆排序堆下面数组进行排序(最大堆)
int[] arr = {3, 5, 1, 6, 4, 7, 2};
堆排序的详细步骤如下:
第一步:构建最大堆
我们需要将数组转换成最大堆。我们从最后一个非叶子节点开始(即数组中间位置的节点),到根节点位置进行调整。对于给定数组,最后一个非叶子节点的索引是 (arr.length - 1) / 2
。
-
(7 - 1) / 2 = 3 -> 第3个位置的元素
6
开始调整,确保它的子树遵循最大堆的性质。因为它的孩子4
和7
已经满足最大堆的性质(7 > 6 > 4),所以不必调整。 -
接下来是位置2的元素
1
,现在我们需要调整以确保子树满足最大堆性质。1
需要和它的孩子6
和4
中较大的一个交换,即6
。交换后的数组如下:int[] arr = {3, 5, 6, 1, 4, 7, 2};
-
现在是位置1的元素
5
。它的孩子节点是1
和4
,都已满足最大堆的性质,不必调整。 -
最后,我们调整根节点位置0的元素
3
。它与两个子节点5</