当前位置: 首页 > article >正文

每日一题——小根堆实现堆排序算法

小根堆实现堆排序算法

    • 堆排序的基本思想
      • 堆排序的步骤
    • 实现步骤
      • 1. 构建小根堆
      • 2. 删除最小元素并调整堆
    • C语言实现
    • 输出示例
    • 代码解释
      • 1. percolateDown 函数
      • 2. buildMinHeap 函数
      • 3. heapSort 函数
      • 4. printArray函数
    • 排序过程详解
      • 步骤1:构建小根堆
      • 步骤2:删除堆顶元素并调整堆
      • 最终结果
    • 总结

堆排序是一种基于堆数据结构的排序算法,利用堆的性质来高效地对数组进行排序。堆排序的时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),而且是一种原地排序算法,空间复杂度为 O ( 1 ) O(1) O(1)

堆排序的基本思想

堆排序的核心思想是利用小根堆的性质,将数组构建成一个小根堆,然后逐步删除堆顶元素(最小值),并将其放到数组的末尾。通过重复这个过程,数组最终会被排序。

堆排序的步骤

  1. 构建小根堆:将数组构建成一个小根堆。
  2. 删除最小元素:每次删除堆顶元素(最小值),并将堆的最后一个元素移到堆顶,然后调整堆以保持小根堆的性质。
  3. 重复步骤2:直到堆为空,此时数组已经被排序。

实现步骤

1. 构建小根堆

从最后一个非叶子节点开始,逐个向下调整,直到根节点。最后一个非叶子节点的索引为 $ \left\lfloor \frac{2n - 2}{2} \right\rfloor $,其中 n n n 是数组的长度。

2. 删除最小元素并调整堆

每次删除堆顶元素,将其放到数组的末尾,然后将堆的最后一个元素移到堆顶,再进行向下调整。

C语言实现

#include <stdio.h>
#include <stdlib.h>

// 向下调整堆
void percolateDown(int* arr, int n, int i) {
    int smallest = i; // 当前节点索引
    int left = 2 * i + 1; // 左子节点索引
    int right = 2 * i + 2; // 右子节点索引
	//找到父子结点中的最小节点索引
    // 如果左子节点存在且小于当前节点的值
    if (left < n && arr[left] < arr[smallest]) 
        smallest = left;
        
    // 如果右子节点存在且小于当前最小值
    if (right < n && arr[right] < arr[smallest])
        smallest = right;
    

    // 如果最小值不是当前节点,交换并继续调整堆
    if (smallest != i) {
        int temp = arr[i];
        arr[i] = arr[smallest];
        arr[smallest] = temp;
        percolateDown(arr, n, smallest);  // 递归调整
    }
}

// 构建小根堆
void buildMinHeap(int* arr, int n) {
    // 从最后一个**非叶子节点**开始调整后一大半都是叶子节点
    for (int i = (n - 2) / 2; i >= 0; i--) {
        percolateDown(arr, n, i);
    }
}

// 堆排序
void heapSort(int* arr, int n) {
    // 构建小根堆
    buildMinHeap(arr, n);

    // 逐个删除最小元素并调整堆
    for (int i = n - 1; i >= 0; i--) {
        // 将堆顶元素(最小值)放到数组末尾
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;

        // 调整剩余的堆
        percolateDown(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[] = {3, 1, 6, 5, 2, 4}; // 待排序数组
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("Original array: ");
    printArray(arr, n);

    heapSort(arr, n);  // 执行堆排序

    printf("Sorted array: ");
    printArray(arr, n);  // 输出排序后的数组

    return 0;
}

输出示例

运行上述代码,输出如下:

Original array: 3 1 6 5 2 4 
Sorted array: 1 2 3 4 5 6 

代码解释

1. percolateDown 函数

这个函数用于向下调整堆,确保当前节点的值小于其子节点的值。通过比较当前节点、左子节点和右子节点的值,找到最小值的索引。如果最小值不是当前节点,交换它们,并递归调整子树。

2. buildMinHeap 函数

从最后一个非叶子节点开始,逐个向下调整,直到根节点。最后一个非叶子节点的索引为 ⌊ 2 n − 2 2 ⌋ \left\lfloor \frac{2n - 2}{2} \right\rfloor 22n2

3. heapSort 函数

首先调用 buildMinHeap 构建小根堆。然后逐个删除堆顶元素(最小值),将其放到数组末尾。每次删除后,调整剩余的堆,确保堆的性质仍然成立。

4. printArray函数

该函数用于打印数组,方便查看排序结果。

排序过程详解

假设我们有一个数组 arr[] = {3, 1, 6, 5, 2, 4},以下是堆排序的详细过程:

步骤1:构建小根堆

  1. 构建小根堆:从最后一个非叶子节点开始向下调整,直到根节点。
    • arr[] = {3, 1, 6, 5, 2, 4}
    • 从索引 2(值为 6)开始:
      • 左子节点 5,右子节点 4,最小值为 4,交换 64,调整得到 arr[] = {3, 1, 4, 5, 2, 6}
    • 继续调整索引 1(值为 1):
      • 左子节点 5,右子节点 2,最小值为 2,交换 12,调整得到 arr[] = {3, 2, 4, 5, 1, 6}
    • 最后调整索引 0(值为 3):
      • 左子节点 2,右子节点 4,最小值为 2,交换 32,调整得到 arr[] = {2, 3, 4, 5, 1, 6}
      • 接着,索引 1 的值为 3,继续向下调整,最终得到 arr[] = {2, 1, 4, 5, 3, 6}

此时,堆构建完成。

步骤2:删除堆顶元素并调整堆

  1. 删除堆顶元素并调整堆
    • 第一次:
      • 删除堆顶元素 2,将堆顶替换为堆的最后一个元素 6,得到 arr[] = {6, 1, 4, 5, 3}
      • 调整堆,交换 61,然后交换 63,得到 arr[] = {1, 3, 4, 5, 6}
    • 第二次:
      • 删除堆顶元素 1,将堆顶替换为堆的最后一个元素 6,得到 arr[] = {6, 3, 4, 5}
      • 调整堆,交换 63,得到 arr[] = {3, 6, 4, 5},然后交换 65,得到 arr[] = {3, 5, 4, 6}
    • 第三次:
      • 删除堆顶元素 3,将堆顶替换为堆的最后一个元素 6,得到 arr[] = {6, 5, 4}
      • 调整堆,交换 64,得到 arr[] = {4, 5, 6}
    • 第四次:
      • 删除堆顶元素 4,将堆顶替换为堆的最后一个元素 6,得到 arr[] = {6, 5}
      • 调整堆,交换 65,得到 `arr[] =

{5, 6}`。

  • 第五次:
    • 删除堆顶元素 5,将堆顶替换为堆的最后一个元素 6,得到 arr[] = {6}
    • 此时,堆中只剩一个元素,排序完成。

最终结果

排序后的数组为 arr[] = {1, 2, 3, 4, 5, 6}

总结

堆排序利用小根堆的性质,通过构建堆、删除堆顶元素并调整堆的方式进行排序。它的时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),空间复杂度为 O ( 1 ) O(1) O(1),是一种原地排序算法,不稳定排序适用于大规模数据的排序任务。


http://www.kler.cn/a/532298.html

相关文章:

  • X Window System 架构概述
  • 机器学习10
  • 【C++】继承(下)
  • 技术架构师成长路线(2025版)
  • 浅谈《图解HTTP》
  • js笔记(黑马程序员)
  • 低通滤波算法的数学原理和C语言实现
  • vim-plug的自动安装与基本使用介绍
  • 【学术征稿-组织单位 武汉理工大学西安理工大学、西安财经大学】第三届通信网络与机器学习(CNML 2025)
  • Codeforces Round 1002 (Div. 2)(部分题解)
  • 利用Python高效处理大规模词汇数据
  • MongoDB 聚合
  • 简易CPU设计入门:指令单元(三)
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.29 NumPy+Scikit-learn(sklearn):机器学习基石揭秘
  • DeepSeek蒸馏模型:轻量化AI的演进与突破
  • 测试csdn图片发布
  • 为何在Kubernetes容器中以root身份运行存在风险?
  • 机器学习在环境科学中的应用
  • BUU16 [ACTF2020 新生赛]BackupFile1
  • 通信易懂唠唠SOME/IP——SOME/IP 协议规范
  • 分布式微服务系统架构第91集:系统性能指标总结
  • 额外题目汇总1:数组
  • deepseek出现以后国产AI大降价--分析各品牌AI的分效用和价格
  • 华为云kubernetes部署deepseek r1、ollama和open-webui(已踩过坑)
  • Linux进程概念
  • ELF2开发板(飞凌嵌入式)部署yolov5s的自定义模型