5-4 其他排序:堆排序

堆排序

堆排序是一种基于二叉堆数据结构的比较排序算法。

  • 它是选择排序算法的优化版本。
  • 该算法反复查找最大(或最小)元素,并将其与最后一个(或第一个)元素交换。
  • 使用二叉堆可以在 O(log n) 时间内高效访问最大(或最小)元素,而不是 O(n)。
  • 对剩余元素重复此过程,直到数组排序完成。
  • 总体而言,堆排序的时间复杂度为 O(n log n)。

堆排序算法
首先使用堆化(heapify)将数组转换为最大堆。请注意,此操作是原地进行的。数组元素会重新排列以符合堆的属性。然后,逐个删除最大堆的根节点,并将其替换为最后一个节点,然后再次进行堆化。重复此过程,直到堆的大小大于 1。

堆排序的详细工作原理
步骤 1:将数组视为完全二叉树
我们首先需要将数组可视化为一棵完全二叉树。对于大小为 n 的数组,根节点位于索引 0,索引为 i 的元素的左子节点位于 2i + 1,右子节点位于 2i + 2。
image
步骤 2:构建最大堆
image
image
image
image
image
image
image
步骤 3:对数组进行排序,将最大元素放在未排序数组的末尾。
image
image
image
image
image
image
上图展示了对数组进行排序的一些步骤。我们需要重复这些步骤,直到堆中只剩下一个元素为止。


代码实现

#include <iostream>
#include <vector>

// To heapify a subtree rooted with node i
void heapify(std::vector<int>& arr, int n, int i)
{
    // Initialize largest as root
    int largest = i;

    // left index = 2*i + 1
    int l = 2 * i + 1;

    // right index = 2*i + 2
    int r = 2 * i + 2;

    // If left child is larger than root
    if (l < n && arr[l] > arr[largest])
    {
        largest = l;
    }

    // If right child is larger than largest so far
    if (r < n && arr[r] > arr[largest])
    {
        largest = r;
    }

    // If largest is not root
    if (largest != i)
    {
        std::swap(arr[i], arr[largest]);

        // Recursively heapify the affected sub-tree
        heapify(arr, n, largest);
    }
}

// Main function to do heap sort
void heapSort(std::vector<int>& arr)
{
    int n = arr.size();

    // Build heap (rearrange vector)
    for (int i = n / 2 - 1; i >= 0; i--)
    {
        heapify(arr, n, i);
    }

    // One by one extract an element from heap
    for (int i = n - 1; i > 0; i--)
    {
        // Move current root to end
        std::swap(arr[0], arr[i]);

        // Call max heapify on the reduced heap
        heapify(arr, i, 0);
    }
}

int main()
{
    std::vector<int> arr = { 9, 4, 3, 8, 10, 2, 5 };

    heapSort(arr);

    for (int i = 0; i < arr.size(); ++i)
    {
        std::cout << arr[i] << " ";
    }

    return 0;
}

输出:
image


堆排序的复杂度、要点、以及优缺点:

堆排序的复杂度
时间复杂度: O(n log n);
辅助空间: O(log n),这是由于递归调用栈造成的。但是,对于迭代实现,辅助空间可以达到 O(1)。

关于堆排序的要点

  • 原地算法。
  • 它的典型实现方式并不稳定,但可以通过一些方法使其稳定(参见此处)。
  • 通常比实现良好的快速排序慢 2-3 倍。速度慢的原因是缺乏局部性引用。

堆排序的优点

  • 高效的时间复杂度:堆排序在任何情况下都保证时间复杂度为 O(n log n),使其适用于大型数据集。log n 因子来源于二叉堆的高度,从而保证了性能的稳定性。
  • 内存占用极低:堆排序可以原地执行,仅需极少的额外内存。使用迭代式的 heapify() 函数可以避免占用额外的栈空间,因此除了存储数组本身之外,无需额外的内存。
  • 简单性:与其他高效排序算法相比,堆排序相对容易理解和实现,因为它依赖于简单的二叉堆结构,而没有递归等高级概念(如果使用迭代堆化)。

堆排序的缺点

  • 成本高昂:堆排序的成本较高,因为即使堆排序和归并排序的时间复杂度均为 O(n log n),堆排序的常数项也比归并排序高。
  • 不稳定:堆排序不稳定,可能会改变元素的相对顺序。
  • 效率低下:堆排序效率不高,因为其时间复杂度中存在较大的常数。
posted @ 2026-03-30 18:44  游翔  阅读(0)  评论(0)    收藏  举报