微信登录

堆排序 - 算法原理 - 堆排序的基本思想

堆排序 - 算法原理 - 堆排序的基本思想

在计算机科学的浩瀚算法海洋中,堆排序宛如一颗璀璨的明珠,以其高效和稳定的特性在排序算法领域占据着重要的一席之地。下面我们就来深入探究堆排序的基本思想、原理以及实际应用。

一、堆的基本概念

在了解堆排序之前,我们需要先认识一下“堆”这种数据结构。堆是一种特殊的完全二叉树,它分为两种类型:最大堆和最小堆。

1. 最大堆

最大堆的特点是每个节点的值都大于或等于其子节点的值。也就是说,根节点是整个堆中的最大值。例如,一个最大堆可以表示为以下形式:

  1. 9
  2. / \
  3. 7 8
  4. / \ / \
  5. 3 6 4 5

在这个最大堆中,根节点 9 大于其左右子节点 7 和 8,7 大于其子节点 3 和 6,8 大于其子节点 4 和 5。

2. 最小堆

最小堆则与最大堆相反,每个节点的值都小于或等于其子节点的值,根节点是整个堆中的最小值。例如:

  1. 1
  2. / \
  3. 2 3
  4. / \ / \
  5. 4 5 6 7

在这个最小堆中,根节点 1 小于其左右子节点 2 和 3,2 小于其子节点 4 和 5,3 小于其子节点 6 和 7。

二、堆排序的基本思想

堆排序的基本思想是利用堆这种数据结构的特性,通过构建堆和不断调整堆来实现排序。具体步骤可以分为以下两个主要阶段:

1. 构建初始堆

首先,将待排序的数组构建成一个堆。如果要进行升序排序,我们通常构建最大堆;如果要进行降序排序,则构建最小堆。以升序排序为例,构建最大堆的过程是从最后一个非叶子节点开始,依次对每个节点进行“调整”操作,使其满足最大堆的性质。

2. 排序过程

在构建好初始堆后,堆的根节点就是最大值。将根节点与数组的最后一个元素交换位置,此时最大值就被放到了数组的末尾。然后,将剩余的元素重新调整为最大堆,再将新的根节点(即剩余元素中的最大值)与倒数第二个元素交换位置,依此类推,直到整个数组有序。

三、堆排序的实现步骤

1. 构建最大堆

假设我们有一个待排序的数组 [4, 10, 3, 5, 1],下面是构建最大堆的详细过程:

步骤 数组状态 操作
1 [4, 10, 3, 5, 1] 从最后一个非叶子节点开始调整,最后一个非叶子节点的索引为 (n - 2) / 2,这里 n = 5,所以索引为 1,对应元素 10。由于 10 大于其子节点 5 和 1,无需调整。
2 [4, 10, 3, 5, 1] 处理索引为 0 的节点 4,4 小于其子节点 10,交换 4 和 10 的位置,得到 [10, 4, 3, 5, 1]。然后检查交换后的 4 是否满足最大堆性质,4 小于其子节点 5,交换 4 和 5 的位置,得到 [10, 5, 3, 4, 1]

最终构建好的最大堆对应的数组为 [10, 5, 3, 4, 1]

2. 排序过程

步骤 数组状态 操作
1 [10, 5, 3, 4, 1] 将根节点 10 与最后一个元素 1 交换位置,得到 [1, 5, 3, 4, 10]。然后将前 4 个元素重新调整为最大堆,得到 [5, 4, 3, 1, 10]
2 [5, 4, 3, 1, 10] 将根节点 5 与倒数第二个元素 1 交换位置,得到 [1, 4, 3, 5, 10]。再将前 3 个元素重新调整为最大堆,得到 [4, 1, 3, 5, 10]
3 [4, 1, 3, 5, 10] 将根节点 4 与倒数第三个元素 3 交换位置,得到 [3, 1, 4, 5, 10]。接着将前 2 个元素重新调整为最大堆,得到 [3, 1, 4, 5, 10]
4 [3, 1, 4, 5, 10] 将根节点 3 与倒数第四个元素 1 交换位置,得到 [1, 3, 4, 5, 10],此时数组已经有序。

四、代码实现(Python)

  1. def heapify(arr, n, i):
  2. largest = i
  3. l = 2 * i + 1
  4. r = 2 * i + 2
  5. if l < n and arr[i] < arr[l]:
  6. largest = l
  7. if r < n and arr[largest] < arr[r]:
  8. largest = r
  9. if largest!= i:
  10. arr[i], arr[largest] = arr[largest], arr[i]
  11. heapify(arr, n, largest)
  12. def heapSort(arr):
  13. n = len(arr)
  14. # 构建最大堆
  15. for i in range(n // 2 - 1, -1, -1):
  16. heapify(arr, n, i)
  17. # 一个个交换元素
  18. for i in range(n - 1, 0, -1):
  19. arr[i], arr[0] = arr[0], arr[i]
  20. heapify(arr, i, 0)
  21. return arr
  22. # 测试
  23. arr = [4, 10, 3, 5, 1]
  24. sorted_arr = heapSort(arr)
  25. print("排序后的数组:", sorted_arr)

五、复杂度分析

  • 时间复杂度:堆排序的时间复杂度为 $O(n log n)$,其中 $n$ 是数组的长度。构建初始堆的时间复杂度为 $O(n)$,每次调整堆的时间复杂度为 $O(log n)$,总共需要进行 $n - 1$ 次交换和调整操作,因此总的时间复杂度为 $O(n log n)$。
  • 空间复杂度:堆排序只需要常数级的额外空间,因此空间复杂度为 $O(1)$。

六、总结

堆排序是一种高效的排序算法,它利用堆这种数据结构的特性,通过构建堆和不断调整堆来实现排序。堆排序的时间复杂度稳定在 $O(n log n)$,且不需要额外的存储空间,适用于大规模数据的排序。同时,堆排序的思想还可以应用于其他领域,如优先队列的实现等。通过深入理解堆排序的基本思想和原理,我们可以更好地运用这一算法解决实际问题。