堆排序

定义与基本思想

堆排序(Heap Sort)是一种利用堆这种数据结构设计的高效排序算法。其基本思想是:将序列构建成大顶堆(或小顶堆),每次取堆顶元素放到序列末尾,调整剩余元素为新堆,重复直到全部有序。

堆排序

点击“执行一步”观察算法状态变化。

算法描述

  1. 将待排序序列建成大顶堆
  2. 取出堆顶元素(最大值),与末尾元素交换,堆长度减 1
  3. 调整剩余元素为新大顶堆
  4. 重复步骤 2-3,直到堆长度为 1

伪代码如下:

function heapSort(A, n):
    buildMaxHeap(A, n)
    for i = n-1 downto 1:
        swap(A[0], A[i])
        heapify(A, 0, i)

function buildMaxHeap(A, n):
    for i = n/2-1 downto 0:
        heapify(A, i, n)

function heapify(A, i, n):
    largest = i
    l = 2*i+1
    r = 2*i+2
    if l < n and A[l] > A[largest]:
        largest = l
    if r < n and A[r] > A[largest]:
        largest = r
    if largest != i:
        swap(A[i], A[largest])
        heapify(A, largest, n)

堆的定义与性质

  • 堆是一棵完全二叉树,用数组存储
  • 大顶堆:每个结点的值都大于等于其左右孩子,堆顶是最大值
  • 小顶堆:每个结点的值都小于等于其左右孩子,堆顶是最小值
  • 结点 i 的左孩子为 2i+1,右孩子为 2i+2,双亲为 ⌊(i−1)/2⌋\lfloor (i-1)/2 \rfloor

代码实现

// 将以 i 为根结点的子树调整为大顶堆
void Heapify(int A[], int i, int n) {
    int largest = i;
    int l = 2 * i + 1, r = 2 * i + 2;
    if (l < n && A[l] > A[largest]) largest = l;
    if (r < n && A[r] > A[largest]) largest = r;
    if (largest != i) {
        int tmp = A[i]; A[i] = A[largest]; A[largest] = tmp;
        Heapify(A, largest, n);  // 递归调整子树
    }
}

// 建堆:从最后一个非叶子结点开始,自底向上调整
void BuildMaxHeap(int A[], int n) {
    for (int i = n / 2 - 1; i >= 0; i--)
        Heapify(A, i, n);
}

// 堆排序
void HeapSort(int A[], int n) {
    BuildMaxHeap(A, n);
    for (int i = n - 1; i > 0; i--) {
        int tmp = A[0]; A[0] = A[i]; A[i] = tmp;  // 堆顶(最大)移到末尾
        Heapify(A, 0, i);                          // 调整剩余部分
    }
}

堆排序与优先队列的关联

堆是优先队列最常用的实现:入队(插入)O(log⁡n)O(\log n),出队(取最大/最小)O(log⁡n)O(\log n)。Dijkstra 算法、任务调度、Top K 问题等场景都用到了堆。

时间复杂度分析

  • 最好/最坏/平均情况:O(nlog⁡n)O(n\log n)
  • 空间复杂度:O(1)O(1)
  • 稳定性:不稳定排序

习题

习题 1

对数组 A=[4,10,3,5,1]A = [4, 10, 3, 5, 1] 进行堆排序,写出初始建堆和第一次交换后的堆结构。

堆排序大顶堆建堆堆调整

解题思路:

本题考查堆排序的初始建堆与第一次交换。核心思想是先把序列调整成大顶堆使最大元素位于堆顶,再把堆顶与堆尾元素交换、缩小堆并重新向下调整,使最大元素逐趟归位到序列末尾。

详细步骤:

  1. 初始建堆:[10, 5, 3, 4, 1]
  2. 第一次交换:[1, 5, 3, 4, 10],再调整为新堆:[5, 4, 3, 1, 10]
  3. 说明:交换后堆顶为 1 而它的孩子为 5,不满足大顶堆性质,需要向下调整,最终得到新堆 [5, 4, 3, 1, 10],最大元素 10 已归位到序列末尾。

最终答案:

如上,第一次交换后最大元素归位。

习题 2

堆排序的时间复杂度和空间复杂度分别是多少?

堆排序时间复杂度空间复杂度

解题思路:

本题考查堆排序的复杂度分析。建堆自底向上调整,代价为 O(n)O(n);随后做 n−1n-1 次交换与堆调整,每次调整的路径长度不超过 O(log⁡n)O(\log n),故总时间复杂度为 O(nlog⁡n)O(n \log n) 且性能稳定。

详细步骤:

  1. 时间复杂度 O(nlog⁡n)O(n\log n):建堆 O(n)O(n),其后 n−1n-1 趟每趟堆调整 O(log⁡n)O(\log n)。
  2. 空间复杂度 O(1)O(1):原地调整堆结构,只需常数个辅助变量。
  3. 对比参考:快速排序平均 O(nlog⁡n)O(n \log n) 但最坏可达 O(n2)O(n^2);归并排序稳定为 O(nlog⁡n)O(n \log n) 但需要 O(n)O(n) 辅助空间。

最终答案:

时间 O(nlog⁡n)O(n\log n),空间 O(1)O(1)。

发现内容有问题?

告诉我们哪里需要改进。