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

小根堆实现堆排序算法

    • 堆排序的基本思想
      • 堆排序的步骤
    • 实现步骤
      • 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),是一种原地排序算法,不稳定排序适用于大规模数据的排序任务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/964031.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【大数据技术】教程03:本机PyCharm远程连接虚拟机Python

本机PyCharm远程连接虚拟机Python 注意:本文需要使用PyCharm专业版。 pycharm-professional-2024.1.4VMware Workstation Pro 16CentOS-Stream-10-latest-x86_64-dvd1.iso写在前面 本文主要介绍如何使用本地PyCharm远程连接虚拟机,运行Python脚本,提高编程效率。 注意: …

bypass hcaptcha、hcaptcha逆向

可以过steam&#xff0c;已支持并发&#xff0c;欢迎询问&#xff01; 有事危&#xff0c;ProfessorLuoMing

Mac本地部署DeekSeek-R1下载太慢怎么办?

Ubuntu 24 本地安装DeekSeek-R1 在命令行先安装ollama curl -fsSL https://ollama.com/install.sh | sh 下载太慢&#xff0c;使用讯雷&#xff0c;mac版下载链接 https://ollama.com/download/Ollama-darwin.zip 进入网站 deepseek-r1:8b&#xff0c;看内存大小4G就8B模型 …

VSCode设置内容字体大小

1、打开VSCode软件&#xff0c;点击左下角的“图标”&#xff0c;选择“Setting”。 在命令面板中的Font Size处选择适合自己的字体大小。 2、对比Font Size值为14与20下的字体大小。

嵌入式学习---蜂鸣器篇

1. 蜂鸣器分类 蜂鸣器是一种电子发声器件&#xff0c;采用直流电压供电&#xff0c;能够发出声音。广泛应用于计算机、打印机、报警器、电子玩具等电子产品中作为发声部件。一般仅从外形不易分辨蜂鸣器的种类。但是有些蜂鸣器使用广泛&#xff0c;见得多了就很容易分辨。例如常…

解析PHP文件路径相关常量

PHP文件路径相关常量包括以下几个常量&#xff1a; __FILE__&#xff1a;表示当前文件的绝对路径&#xff0c;包括文件名。 __DIR__&#xff1a;表示当前文件所在的目录的绝对路径&#xff0c;不包括文件名。 dirname(__FILE__)&#xff1a;等同于__DIR__&#xff0c;表示当前…

C++底层学习预备:模板初阶

文章目录 1.编程范式2.函数模板2.1 函数模板概念2.2 函数模板原理2.3 函数模板实例化2.3.1 隐式实例化2.3.2 显式实例化 2.4 模板参数的匹配原则 3.类模板希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 进入STL库学习之前我们要先了解有关模板的…

【腾讯前端面试】纯css画图形

之前参加腾讯面试&#xff0c;第一轮是笔试&#xff0c;面试官发的试卷里有一题手写css画一个扇形、一个平行四边形……笔试时间还是比较充裕的&#xff0c;但是我对这题完全没有思路&#x1f62d;于是就空着了&#xff0c;最后也没过。 今天偶然翻到廖雪峰大佬的博客里提到了关…

智慧园区综合管理系统如何实现多个维度的高效管理与安全风险控制

内容概要 在当前快速发展的城市环境中&#xff0c;智慧园区综合管理系统正在成为各类园区管理的重要工具&#xff0c;无论是工业园、产业园、物流园&#xff0c;还是写字楼与公寓&#xff0c;都在积极寻求如何提升管理效率和保障安全。通过快鲸智慧园区管理系统&#xff0c;用…

自制虚拟机(C/C++)(三、做成标准GUI Windows软件,扩展指令集,直接支持img软盘)

开源地址:VMwork 要使终端不弹出&#xff0c; #pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") 还要实现jmp near 0x01类似的 本次的main.cpp #include <graphics.h> #include <conio.h> #include <windows.h> #includ…

如何确认Linux嵌入式系统的触摸屏对应的是哪个设备文件(/dev/input/event1)?如何查看系统中所有的输入设备?输入设备的设备文件有什么特点?

Linux嵌入式系统的输入设备的设备文件有什么特点&#xff1f; 在 Linux 中&#xff0c;所有的输入设备&#xff08;如键盘、鼠标、触摸屏等&#xff09;都会被内核识别为 输入事件设备&#xff0c;并在 /dev/input/ 目录下创建相应的 设备文件&#xff0c;通常是&#xff1a; …

HTTP异步Client源码解析

我们知道Netty作为高性能通信框架&#xff0c;优点在于内部封装了管道的连接通信等操作&#xff0c;用户只需要调用封装好的接口&#xff0c;便可以很便捷的进行高并发通信。类似&#xff0c;在Http请求时&#xff0c;我们通过调用HttpClient&#xff0c;内部使用java NIO技术&…

利用Vue和javascript分别编写一个“Hello World”的定时更新

目录 一、利用Vue编写一个“Hello World”的定时更新&#xff08;1&#xff09;vue编码在Html文件中&#xff08;2&#xff09;vue编码在js文件中 二、利用javascript编写一个“Hello World”的定时更新 一、利用Vue编写一个“Hello World”的定时更新 &#xff08;1&#xff…

leetcode27.删除有序数组中的重复项

目录 问题描述判题标准示例提示 具体思路思路一思路二 代码实现 问题描述 给你一个非严格递增排列的数组nums&#xff0c;请你原地删除重复出现的元素&#xff0c;使每个元素只出现一次&#xff0c;返回删除后数组的新长度。元素的相对顺序应该保持一致 。然后返回nums中唯一元…

Vue 图片引用方式详解:静态资源与动态路径访问

目录 前言1. 引用 public/ 目录2. assets/ 目录3. 远程服务器4. Vue Router 动态访问5. 总结6. 扩展&#xff08;图片不显示&#xff09; 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 在 Vue 开发中&#x…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.18 对象数组:在NumPy中存储Python对象

2.18 对象数组&#xff1a;在NumPy中存储Python对象 目录 #mermaid-svg-shERrGOBuM2rBzeB {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-shERrGOBuM2rBzeB .error-icon{fill:#552222;}#mermaid-svg-shERrGOBuM2rB…

Java 大视界 -- Java 大数据在自动驾驶中的数据处理与决策支持(68)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

pstricks PGFTikz 在CTeX套装中绘图Transparency或Opacity失效的问题

我在CTeX中画图的时候&#xff0c;习惯用Geogebra先画好&#xff0c;然后生成pstricks或PGFTikz代码&#xff1a; 这样不用插入eps或pdf之类的图片&#xff0c;也是一种偷懒的方法。以前往arXiv.org上面传论文也是这样&#xff1a;代码出图&#xff0c;就不用另外上传一幅eps或…

deepseek 本地化部署和小模型微调

安装ollama 因为本人gpu卡的机器系统是centos 7, 直接使用ollama会报 所以ollama使用镜像方式进行部署&#xff0c; 拉取镜像ollama/ollama 启动命令 docker run -d --privileged -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama 查看ollama 是否启动…

Java_类加载器

小程一言类加载器的基础双亲委派模型核心思想优势 各类加载器的职责 类加载器的工作流程举例&#xff1a;如何在Java中使用类加载器启动类加载器、扩展类加载器与系统类加载器输出解释自定义类加载器 类加载器与类冲突总结 小程一言 本专栏是对Java知识点的总结。在学习Java的过…