13.堆排序:基于完全二叉树的高效排序算法一、什么是堆排序?

一、什么是堆排序?

堆排序(Heap Sort)是一种基于完全二叉树的高效排序算法,它利用堆的性质(最大堆或最小堆)将数组转换为有序序列。

简单来说,堆排序就像 "养猪场选猪王":

  • 把数组元素看作一群猪,堆的结构就像金字塔,越上层的猪体重越大;
  • 先把猪群整理成最大堆(最上面的猪是最重的);
  • 每次把最上面的猪(最大元素)放到数组末尾,再把剩下的猪重新整理成最大堆;
  • 重复这个过程,直到所有猪都按体重从小到大排好序。

二、堆的核心性质

堆是一种特殊的完全二叉树,分为两种:

  1. 最大堆:每个父节点的值都大于或等于它的子节点的值,根节点是整个堆的最大值;
  2. 最小堆:每个父节点的值都小于或等于它的子节点的值,根节点是整个堆的最小值。

堆排序通常使用最大堆,因为这样可以每次取出最大值,放到数组末尾,最终得到从小到大的有序数组。

三、堆排序的核心步骤

堆排序的核心步骤可以分为以下两步:

  1. 构建最大堆:将数组转换为最大堆,从最后一个非叶子节点开始,向上调整每个节点,使其满足最大堆的性质;
  2. 排序:每次将堆顶元素(最大值)与数组末尾元素交换,然后将剩下的元素重新调整为最大堆,重复这个过程直到数组有序。

四、堆排序的代码实现

1. Python 版本(直观易懂)

复制代码
def heapify(arr, n, i):
    # 假设当前节点i是最大的
    largest = i
    # 左子节点的索引
    left = 2 * i + 1
    # 右子节点的索引
    right = 2 * i + 2

    # 如果左子节点比当前节点大,更新最大节点索引
    if left < n and arr[left] > arr[largest]:
        largest = left

    # 如果右子节点比当前节点大,更新最大节点索引
    if right < n and arr[right] > arr[largest]:
        largest = right

    # 如果最大节点不是当前节点,交换并继续调整
    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]
        # 递归调整交换后的子树
        heapify(arr, n, largest)

def heap_sort(arr):
    n = len(arr)
    # 第一步:构建最大堆,从最后一个非叶子节点开始
    for i in range(n // 2 - 1, -1, -1):
        heapify(arr, n, i)

    # 第二步:排序,逐个取出最大值
    for i in range(n - 1, 0, -1):
        # 将堆顶元素(最大值)与数组末尾元素交换
        arr[i], arr[0] = arr[0], arr[i]
        # 调整剩下的元素为最大堆
        heapify(arr, i, 0)

# 测试
arr = [80, 150, 100, 60, 170, 120, 90]
print("排序前:", arr)
heap_sort(arr)
print("排序后:", arr)  # 输出:[60, 80, 90, 100, 120, 150, 170]

2. C 语言版本(更贴近底层)

复制代码
#include <stdio.h>

// 交换两个元素
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 调整堆,使以i为根的子树满足最大堆性质
void heapify(int arr[], int n, int i) {
    int largest = i; // 假设当前节点i是最大的
    int left = 2 * i + 1; // 左子节点索引
    int right = 2 * i + 2; // 右子节点索引

    // 如果左子节点比当前节点大,更新最大节点索引
    if (left < n && arr[left] > arr[largest]) {
        largest = left;
    }

    // 如果右子节点比当前节点大,更新最大节点索引
    if (right < n && arr[right] > arr[largest]) {
        largest = right;
    }

    // 如果最大节点不是当前节点,交换并继续调整
    if (largest != i) {
        swap(&arr[i], &arr[largest]);
        // 递归调整交换后的子树
        heapify(arr, n, largest);
    }
}

// 堆排序函数
void heap_sort(int arr[], int n) {
    // 第一步:构建最大堆,从最后一个非叶子节点开始
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }

    // 第二步:排序,逐个取出最大值
    for (int i = n - 1; i > 0; i--) {
        // 将堆顶元素(最大值)与数组末尾元素交换
        swap(&arr[0], &arr[i]);
        // 调整剩下的元素为最大堆
        heapify(arr, i, 0);
    }
}

// 打印数组
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {80, 150, 100, 60, 170, 120, 90};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("排序前:");
    printArray(arr, n);

    heap_sort(arr, n);

    printf("排序后:");
    printArray(arr, n); // 输出:60 80 90 100 120 150 170

    return 0;
}

五、堆排序的时间复杂度和空间复杂度

  • 时间复杂度:O (n log n),构建最大堆的时间复杂度为 O (n),排序过程中每次调整堆的时间复杂度为 O (log n),共进行 n 次调整;
  • 空间复杂度:O (1),堆排序是原地排序算法,不需要额外的内存空间;
  • 稳定性:不稳定,因为交换操作可能会改变相同元素的相对位置。

六、堆排序的优化

为了提高堆排序的效率,可以进行以下优化:

  1. 迭代实现 heapify:使用迭代代替递归,减少递归调用栈的深度,避免栈溢出;
  2. 使用最小堆:如果需要得到从大到小的有序数组,可以使用最小堆;
  3. 小数组优化:当数组的大小小于某个阈值(如 10)时,使用插入排序代替堆排序,因为插入排序在小规模数据上效率更高。

七、堆排序的实际应用场景

堆排序是一种高效、稳定的排序算法,常见场景包括:

  1. 大规模数据排序:堆排序的时间复杂度稳定为 O (n log n),适合大规模数据排序;
  2. 内存受限场景:堆排序是原地排序算法,不需要额外的内存空间;
  3. 优先队列:堆是优先队列的底层实现,用于动态获取最大值或最小值;
  4. Top K 问题:使用堆可以高效地从大量数据中找出前 K 个最大或最小的元素。

八、总结

堆排序是一种基于完全二叉树的高效排序算法,它利用堆的性质将数组转换为有序序列,核心步骤是构建最大堆和排序。

堆排序的时间复杂度为 O (n log n),空间复杂度为 O (1),是一种稳定、高效的排序算法,适合大规模数据排序和内存受限场景。

希望这篇文章能帮助你理解堆排序的原理和实现!

相关推荐
白帽攻防录1 小时前
SRC 挖洞:PostScript 打印机驱动双 RCE,打印服务器怎么变成内网跳板
开发语言·网络安全·php·内网安全
十年Java程序媛1 小时前
Java枚举Enum完整教程|替代静态常量,开发与面试要点
java·开发语言·spring boot·后端·面试
2603_965148112 小时前
电商选品调研:通过关键词搜索API获取市场热销商品,结合商品详情API分析竞品卖点与定价
开发语言·数据库·人工智能·python·信息可视化
生态学者2 小时前
Global Change Biology | 森林越“步调一致”,反而越怕干旱?树木生长与叶片活动的同步之谜
大数据·人工智能·算法·r语言·微信公众平台
xixiaoyunya3 小时前
Python 异步编程入门:用 asyncio 轻松实现并发任务
开发语言·python·php
lsswear3 小时前
Agent 框架新手入门与实战部署指南
大数据·人工智能·算法
学逆向的3 小时前
C 循环结构的执行效率分析
c语言·开发语言·数据结构·网络安全
小坏讲微服务3 小时前
刚发布一个月的JDK 27,为什么我建议你立刻升级?
java·开发语言·spring boot·spring·架构·求职招聘·springboot4
Data analyse4564 小时前
rnweb 回放数据量太大怎么办?分片存储与断点续传的实现思路
前端·网络·算法·数据分析
打工仔折腾 AI5 小时前
用 Dashlet 把极空间 NAS 服务入口整理成统一导航首页的实践
java·开发语言·人工智能·后端·python·rpc·ai agent 实战