C语言经典算法之堆排序算法

news2024/11/16 16:44:34

目录

前言

建议

简介

A.建堆:

B.排序

一、代码实现

二、时空复杂度

A.时间复杂度

B.空间复杂度

三、稳定性

四、现实中的应用


前言

建议

1.学习算法最重要的是理解算法的每一步,而不是记住算法。

2.建议读者学习算法的时候,自己手动一步一步地运行算法。

tips:文中的对数均以2为底数

简介

堆排序是一种基于堆数据结构的排序算法。它分为两个主要步骤:建堆和排序。

A.建堆:


建堆的过程是将一个无序的数组构建成一个堆,通常采用最大堆(Max Heap)的形式。最大堆是一种特殊的二叉树,其中每个节点的值都大于或等于其子节点的值。下面是建堆的简单步骤

1.从最后一个非叶子节点开始: 最后一个非叶子节点的索引可以通过 (n/2 - 1) 计算,其中 n 是数组的长度。非叶子节点是指有子节点的节点。

2.进行堆调整(Heapify): 对于每个非叶子节点,进行堆调整,将当前节点与其子节点比较,确保当前节点是最大的。如果子节点的值较大,则交换节点值,然后递归地调整子树。

3.逐层向上重复: 重复步骤2,逐层向上处理非叶子节点,直到根节点。这样就确保了整个数组构成了一个最大堆。

B.排序

排序的过程是将一个无序的数据集合按照一定规则重新排列成有序的序列。以下是一般排序算法的简单过程

1.比较元素: 排序算法的第一步是通过比较元素来确定它们的相对顺序。比较可以根据不同的规则进行,例如升序或降序。

2.交换元素: 根据比较的结果,可能需要交换元素的位置以达到正确的顺序。这是排序算法中一个关键的步骤。

3.重复步骤12: 排序算法会持续比较和交换元素,直到整个数据集合中的元素都按照规则有序排列。

一、代码实现

 
#include <stdio.h>

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

// 调整堆,保持最大堆性质
void maxHeapify(int arr[], int n, int i) {
    int largest = 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]);
        maxHeapify(arr, n, largest);
    }
}

// 建立最大堆
void buildMaxHeap(int arr[], int n) {
    // 从最后一个非叶子节点开始,逐个调整子树使其满足最大堆性质
    for (int i = n / 2 - 1; i >= 0; i--) {
        maxHeapify(arr, n, i);
    }
}

// 堆排序主函数
void heapSort(int arr[], int n) {
    // 建立最大堆
    buildMaxHeap(arr, n);

    // 从最后一个元素开始,逐个将当前最大元素交换到数组末尾
    for (int i = n - 1; i > 0; i--) {
        swap(&arr[0], &arr[i]); // 将当前最大元素移动到数组末尾
        maxHeapify(arr, i, 0);   // 调整堆,保持最大堆性质
    }
}

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

int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("原始数组: ");
    printArray(arr, n);

    heapSort(arr, n);

    printf("堆排序后的数组: ");
    printArray(arr, n);

    return 0;
}

 

这段代码首先建立一个最大堆,然后通过不断交换根节点和最后一个节点,并调整堆的方式,实现堆排序。

二、时空复杂度

A.时间复杂度


堆排序算法的时间复杂度主要由两个部分构成:建堆的时间复杂度排序的时间复杂度

建堆时间复杂度: 建堆的过程需要从最后一个非叶子节点开始,对每个节点进行堆调整(Heapify),使得整个数组构成一个最大堆。建堆的时间复杂度是O(n),其中n是数组的长度。

排序时间复杂度: 在建堆完成后,堆的根节点是数组中的最大元素。将根节点与数组末尾的元素交换,然后重新调整堆,这样就把最大的元素放到了数组末尾。重复这个过程,每次交换后堆的大小减一,直到整个数组有序。由于进行n次交换和调整,排序的时间复杂度也是O(nlogn)

因此,堆排序的总时间复杂度是建堆和排序时间复杂度的和O(n) + O(n * log n) = O(n * log n)

B.空间复杂度

堆排序的空间复杂度主要包括两个方面原地排序所需的空间递归调用的栈空间

原地排序: 堆排序是一种原地排序算法,它不需要额外的辅助数组来存储数据。排序过程中主要是通过在输入数组上进行元素交换,而不需要额外的存储空间。因此,原地排序的空间复杂度为 O(1)。

递归调用的栈空间: 在堆排序的建堆过程中,通常会使用递归调用或迭代方式来实现堆调整。递归调用会在调用栈中占用一定的空间,其空间复杂度与递归深度相关。最坏情况下,堆排序的递归深度为堆的高度,即logn,其中 n 是输入数组的长度。因此,递归调用的空间复杂度为 O(log n)

因此,原地排序和递归调用的空间开销,堆排序的总体空间复杂度为O(1) + O(log n) = O(log n)。这表明堆排序的空间需求相对较低,是一个空间效率较好的排序算法。

三、稳定性

在排序过程中,可能会交换相同值的元素的相对位置,导致相等元素之间的原始相对顺序被打乱。具体来说,在建堆和排序的过程中,会涉及到元素的交换操作。如果两个相等元素的顺序在排序之前是相对的,那么在排序之后,它们的相对顺序可能会发生变化。由于堆排序是通过不断交换堆顶元素和数组末尾元素,并调整堆的过程来实现的,这种交换可能破坏相同元素的相对顺序。因此,堆排序不是稳定的排序算法

四、现实中的应用

堆排序虽然在某些方面不如一些简单的排序算法直观,但在现实中仍然有一些应用场景,其中它的优势得到充分发挥:

优先队列: 堆排序常用于实现优先队列,其中最大堆或最小堆的性质使得可以高效地找到最大或最小元素。这在任务调度、事件模拟等领域有广泛应用。

堆作为数据结构的一部分: 堆结构本身作为一种数据结构,可以被应用于图算法中的最短路径算法(如Dijkstra算法)和最小生成树算法(如Prim和Kruskal算法)等。堆排序作为堆结构的一种附带应用,用于维护和操作堆。

外部排序: 在需要对大规模数据进行排序的情况下,堆排序相对于其他算法的稳定的时间复杂度使得它成为外部排序算法的一种选择。外部排序涉及到将大量数据从外部存储(例如硬盘)加载到内存中进行排序,然后再写回外部存储。

不稳定排序的需求: 在某些情况下,不需要保持相同值元素的相对顺序,而更关注排序算法的性能,此时堆排序可以是一个合适的选择。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度强化学习的变道策略:Harmonious Lane Changing via Deep Reinforcement Learning

偏理论&#xff0c;假设情况不易发生 摘要 多智能体强化学习的换道策略&#xff0c;不同的智能体在每一轮学习后交换策略&#xff0c;达到零和博弈。 和谐驾驶仅依赖于单个车辆有限的感知结果来平衡整体和个体效率&#xff0c;奖励机制结合个人效率和整体效率的和谐。 Ⅰ. 简…

浪花 - 搜索标签前后端联调

前传&#xff1a;浪花 - 根据标签搜索用户-CSDN博客 目录 一、完善后端搜索标签接口 二、前后端搜索标签接口的对接 1. 使用 Axios 发送请求 2. 解决跨域问题 3. Axios 请求传参序列化 4. 接收后端响应数据 5. 处理后端响应数据格式 6. 搜索结果为空的页面展示 附&am…

Linux网络文件共享服务1(基于FTP文件传输协议)

目录 一、了解FTP 1、FTP的相关概念 2、FTP的两种模式及工作原理 2.1 主动模式 2.2 被动模式 3、vsftpd 软件介绍 3.1 基础操作 4、vsftpd服务常见配置 4.1 修改默认命令端口 4.2 主动模式端口 4.3 被动模式端口 4.4 使用当地时间 4.5 匿名用户登录 4.6 匿名用户上…

初识XSS漏洞

目录 一、XSS的原理和分类 二、Xss漏洞分类 1. 反射性xss 简单的演示&#xff1a; 2.基于DOM的XSS 简单的演示&#xff1a; 3.存储型XSS ​编辑简单的演示 4、self xss 三、XSS漏洞的危害 四、XSS漏洞的验证 五、XSS漏洞的黑盒测试 六、XSS漏洞的白盒测试 七、XS…

135基于matlab的经验小波变换(EWT)的自适应信号处理方法

基于matlab的经验小波变换(EWT)的自适应信号处理方法.其核心思想是通过对信号的Fourier谱进行自适应划分,建立合适的小波滤波器组来提取信号不同的成分&#xff0c;EWT1D和EWT2D方法。程序已调通&#xff0c;可直接运行。 135matlab信号处理EWT (xiaohongshu.com)

VMware虚拟机忘记密码操作方法

下面已openEuler虚拟机为例&#xff1a; 1、点击重启时&#xff0c;一直按esc&#xff08;鼠标点击一下&#xff0c;确保鼠标在你的虚拟机里面&#xff09; 2、一直到进入到如下页面按e键&#xff08;可能会略有不同&#xff09; 3、按e键后跳转到如下页面 4、在该页面输入 in…

C++ OJ基础

C OJ基础 在学校学习C程序设计基础课程的OJ题目 缺少第二十题 这里写目录标题 C OJ基础习题练习(一)打印图形习题练习(二)数据的输入输出习题练习(三)函数重载习题练习(四)设计矩形类习题练习(五)定义Tree类习题练习(六)完善职工工资类Salary的设计习题练习(七)设计矩形类recta…

CSS样式学习-基本用法

html超文本传输标签&#xff0c;属性等权重 outline 标签轮廓 <input type"text"> <textarea cols"30" rows"10"></textarea> outline: none; 表示无轮廓 &#xff08;开发时用的比较多&#xff09; CSS 轮廓&#xff…

机器学习-协同过滤

1、协同过滤要解决的问题 协同过滤算法主要用于推荐系统&#xff0c;推荐系统是信息过载所采用的措施&#xff0c;面对海量的数据信息&#xff0c;从中快速推荐出符合用户特点的物品。一些人的“选择恐惧症”、没有明确需求的人。 解决如何从大量信息中找到自己感兴趣的信息。…

【Linux】Linux系统编程——ls命令

【Linux】Linux 系统编程——ls 命令 1.命令概述 ls 命令是 Linux 和其他类 Unix 操作系统中最常用的命令之一。ls 命令是英文单词 list 的缩写&#xff0c;正如 list 的意思&#xff0c;ls 命令用于列出文件系统中的文件和目录。使用此命令&#xff0c;用户可以查看目录中的…

redis之单线程和多线程

目录 1、redis的发展史 2、redis为什么选择单线程&#xff1f; 3、主线程和Io线程是怎么协作完成请求处理的&#xff1f; 4、IO多路复用 5、开启redis多线程 1、redis的发展史 Redis4.0之前是用的单线程&#xff0c;4.0以后逐渐支持多线程 Redis4.0之前一直采用单线程的主…

C++学习笔记——用C++实现树(区别于C)

树是一种非常重要的数据结构&#xff0c;它在计算机科学中的应用非常广泛。在本篇博客中&#xff0c;我们将介绍树的基本概念和C中如何实现树。 目录 一、树的基本概念 2.C中实现树 2.1创建一个树的实例&#xff0c;并向其添加节点 2.2三种遍历方式的实现代码 3.与C语言相…

以前获得的一枚勋章

以前我上大学期间&#xff0c;每周都去合肥南七里买一份广州出版的《足球报》。

数据结构排序二叉树(下)

哎,调了几天深度学习模型,今天来更新排序二叉树 文章目录 前言 一、排序二叉树的结构定义 二、在排序二叉树添加数据 三、定义创建排序二叉树函数 四、查找一棵二叉排序树中的结点x的所在层数 五、删除二叉排序树中T关键字x的节点 六、查找二叉排序树中的所有小于key的关…

QT上位机开发(动画效果)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 不管是仿真&#xff0c;还是对真实环境的一比一模拟&#xff0c;动画都是非常好的一种呈现方式。目前在qt上面&#xff0c;实现动画主要有两种方法…

Github项目推荐--MusicFreeDesktop

项目地址 https://github.com/maotoumao/MusicFreeDesktop 项目简述 这是一个开源的音乐播放器&#xff0c;主要使用typescript编写&#xff0c;页面很漂亮。支持自定义主题和插件化配置音源&#xff0c;是一大亮点。 项目截图

【论文阅读】Latent Consistency Models (LDMs)、LCM-LoRa

文章目录 IntroductionPreliminariesDiffusion ModelsConsistency Models Latent Consistency ModelsConsistency Distillation in the Latent SpaceOne-Stage Guided Distillation by Solving Augmented PF-ODEAccelerating Distillation with Skipping Time StepsLatent Cons…

Rust-数组

数组是一个容器&#xff0c;它在一块连续空间内存中&#xff0c;存储了一系列的同样类型的数据。 数组中元素的占用空间大小必须是编译期确定的。 数组本身所容纳的元素个数也必须是编译期确定的&#xff0c;执行阶段不可变。 如果需要使用变长的容器&#xff0c;可以使用标…

如何使用程序控制微信发送消息

简介 使用杨中科老师的nuget包NetAutoGUI&#xff0c;控制微信给指定用户发送消息&#xff0c;如果想下面视频一样使用此功能用来轰炸朋友&#xff0c;可以直接跳到最后一节&#xff0c;或者直接下载我的打包好的程序集 【免费】控制微信发送消息的程序资源-CSDN文库 微信轰炸…

复合机器人作为一种新型的智能制造装备高效、精准和灵活的生产方式

随着汽车制造业的快速发展&#xff0c;对于高效、精准和灵活的生产方式需求日益增强。复合机器人作为一种新型的智能制造装备&#xff0c;以其独特的优势在汽车制造中发挥着越来越重要的作用。因此&#xff0c;富唯智能顺应时代的发展趋势&#xff0c;研发出了ICR系列的复合机器…