八大算法排序@堆排序(C语言版本)

目录

  • 堆排序
    • 大堆排序
      • 概念
      • 算法思想
        • 建堆
        • 建堆核心算法
        • 建堆的代码
      • 排序代码实现
    • 小堆排序
      • 代码实现
      • 时间复杂度
      • 空间复杂度
    • 特性总结

堆排序

  堆排序借用的是堆的特性来实现排序功能的。大堆需要满足父节点大于子节点,因此堆顶是整个数组中的最大元素。小堆则相反,要求父节点小于子节点,因此父节点是整个数组中最小元素。
借助这一特性,对于大堆,我们可以将堆顶的元素,和堆的最后一个元素置换,这样便将最大的数排到了最后面。同时将堆顶的有效个数减1。但是置换上来堆顶的元素,也就破坏了堆的结构,但是堆顶元素的左右子节点依旧是堆的结构,因此可以对堆顶进行调整,然其继续满足堆的特性。这样便找出第二大的元素,继续重复上述动作,便能将大的数组都往后挪动。待到只剩下一个有效数据时,便拍好了数组。大家认为这是一个升序的数组呢?还是一个降序的数组呢?
  答案显而易见,使用大堆进行排序,结果是升序的效果。同样的思路,使用小堆进行排序,结果是降序的结果。

因此,得出一个结论:
想要对数组进行升序的排序,使用大堆;
想要对数组进行降序的排序,使用小堆;



大堆排序

概念

  大堆概念:每个节点(父节点)的值都大于或等于其子节点的值。
大堆排序借用的便是大堆的特性,将堆顶的元素和堆的最后一个元素进行置换,置换上来堆顶的数据再进行堆结构的调整。然后重复以上动作,实现排序功能。



算法思想

要想实现大堆排序,首先需要对数组建立起大堆的结构。下面我们使用数组 arr[] = { 6 , 4 , 3 , 9 , 2 , 1 ,5 ,7 ,8 };来模拟演示大堆的构建,以及排序的大体过程。如下是数组的初始状态:

数组初始状态

首先,让我们来看看堆的结构是如何产生的。

建堆

  首先,要明确一点的就是:堆是基于完全二叉树而演变出来的一种数据结构。分为最大堆和最小堆两种类型,但它们都是完全二叉树。完全二叉树结构特点如下:

完全二叉树
注意:完全二叉树每个节点必须是从左到右依次遍布的,中间不能跳跃,如第四层的第三个节点,必须是第三层第二个节点的左节点,否则将不满足完全二叉树的特点。

  了解以上知识后,还得须知建堆的核心算法是 “向下调整算法” 。通过算法的一步步调整,一步步建立起堆这一结构。但是!该算法有个前提,就是调整的节点的左右子节点必须是堆的结构。
  有同学可能有疑惑,这不是扯淡吗?我就是要建立的堆结构,又要我满足调整的节点的左右子节点也是堆结构,闹着玩儿呢?
  并不是的,正所谓没有条件,便创造出条件。我们可以从下往上进行建堆啊!比如回到原数组 arr[] = { 6 , 4 , 3 , 9 , 2 , 1 ,5 ,7 ,8 };我们先对数组模拟出堆的结构出来:

原数组

肉眼可见的,原数组模拟出来的完全二叉树,并不符合堆的结构特点。那么如何做到前面所说的,创造出前提条件,进行建堆呢?从堆顶向下进行调整是不可能的,因为堆顶的元素6不满足核心算法的前提条件。但是!我们可以对元素9进行调堆啊,元素9不就正好满足核心算法的前提条件嘛。
首先元素9有左右两个子节点7和8。元素7和元素8左右子节点都为空节点,那么元素7和元素8可以认为是堆的结构,因此元素9左右子节点都是堆结构这一前提条件便满足了。可以使用建堆的核心算法,从元素9开始依次往上建堆。
  元素9建成堆结构后,下标减1。开始对元素3进行建堆,同样的元素3也满足核心算法的前提条件,同样可以使用核心算法进行堆结构的建立。接着就是元素4,元素4此时也满足了左右子节点都是堆结构的条件,同样进行堆的调节建立,最终是元素6也就是堆顶的调节,而这时堆顶也满足了左右子节点都是堆结构的前提条件,同样利用 “向下调整算法” 进行堆结构的建立。至此完成整个数组的堆结构构建。

注:
是如何找到要从哪一个节点进行堆的构建的?利用子节点找父节点的方法!如数组有n个元素,最后一个元素的下标则为n-1,根据完全二叉树的特点,最后一个元素的父节点的下标为:(n-1-1)/ 2。




建堆核心算法
// 两数交换
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 向下调整算法
/*
传入参数:
ret:需要建立的数组
root:需要开始调节的节点
n:数组的个数
*/
void AdjustDown(int* ret,int n,int root)
{int parent = root;// 父节点,也就是要调节的节点int child = parent*2+1;// 子节点/孩子,默认是左节点while(child<n){// 找出左右孩子,最大的孩子,同时要考虑只有左孩子的情况if(child+1<n && ret[child+1] > child[child]){child++;// 如果右孩子比左孩子大,则存储右孩子的下标}// 如果 子节点大于父节点,则子节点向上进行调整if(ret[parent] < ret[child]){swap(ret[parent],ret[child]);parent = child;child=parent*2+1;}else  // 父节点大于子节点则退出循环(注意父节点左右子节点都是堆的结构){break;}}
}




建堆的代码
// 升序 - 建大堆
void CreateHeap(int* a, int n)
{// 建大堆,从最后一个节点的父节点开始建立for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDowm(a, n, i);}
}

建完堆后的数组如下所示:

建堆完成

至此,我们实现了对数组建堆这一个数据结构的条件。接下来便能对其进行排序了。



排序代码实现

void SortHeap(int* a,int n)
{// 排序for (int i = 1; i < n; i++){Swap(&a[0], &a[n - i]);	// 将堆顶元素与堆的最后一个元素交换AdjustDowm(a, n  - i, 0); // 交换上堆顶的元素进行堆的调节,使其依旧保持大堆的结构特性}}

至此,便借用大堆的特性,实现了升序的排序效果。








小堆排序

概念和算法思想和大堆差不多,便不过多冗余介绍了,下面直接给出小堆排序的实现的降序排序代码。




代码实现

// 两数交换
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 向下调整算法 - 前提:左右子树都是堆
void AdjustDowm(int* a, int n, int root)
{int parent = root;int child = parent * 2 + 1;//默认左孩子while (child < n){// 找出左右子节点中,最小的子节点if (child + 1 < n && a[child + 1] < a[child]){child++;}// 如果父节点比子节点大,则将子节点向上调整if (a[parent] > a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;	// 退出循环}}
}// 降序 - 建小堆
void HeapSort(int* a, int n)
{// 建大堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDowm(a, n, i);}//PrintArr(a,n);//查看建堆后的数组// 排序for (int i = 1; i < n; i++){Swap(&a[0], &a[n - i]);AdjustDowm(a, n  - i, 0);}}

与大堆的差别仅改变了核心算法中的两处地方,如下:
在这里插入图片描述

下面介绍一下堆排序的时间复杂度和空间复杂度。




时间复杂度

O(N*logN)
大体计算如下:
建堆时间复杂度为O(N);
堆排序的时间复杂度为O(N*logN),因为将堆顶元素置换以后,还需要进行调堆,调堆的时间复杂度为O(logN)。每排一个元素需要进行一次调堆,所以排完整个数组的时间复杂度为O(N*logN)。
所以总的时间复杂度为:O(N+N*logN)。两者记录较大的,所以时间复杂度为O(N*logN)。




空间复杂度

O(1);

堆排序过程中,都是在原数组上进行的,没有申请空间,所以空间复杂度为O(1)。




特性总结

1、堆排序使用堆来选数,效率就高了很多。
2、时间复杂度:O(N*logN)
3、空间复杂度:O(1)
4、稳定性:不稳定

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

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

相关文章

码农的周末日常---2024/1/6

上周总结 按照规划进行开发&#xff0c;处事不惊&#xff0c;稳稳前行 2024.1.6 天气晴 温度适宜 AM 睡觉前不建议做决定是真的&#xff0c;昨天想着睡到中午&#xff0c;今天九点多醒了&#xff0c;得了&#xff0c;不想睡了 日常三连吧&#xff0c;…

【mars3d】new mars3d.layer.GeoJsonLayer({实现多孔面遮罩mask: true,

【mars3d】new mars3d.layer.GeoJsonLayer({实现多孔面遮罩 官网测试示例&#xff1a; 1.功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 测试代码&#xff1a; export function showDraw(isFlyTo) { removeLayer() const geoJsonLayer new mars3d.layer.GeoJsonLaye…

神经网络-搭建小实战和Sequential的使用

CIFAR-10 model structure 通过已知参数&#xff08;高、宽、dilation1、kernel_size&#xff09;推断stride和padding的大小 网络 import torch from torch import nnclass Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.conv1 nn.Conv2d(in_chan…

TSConfig 配置(tsconfig.json)

详细总结一下TSConfig 的相关配置项。个人笔记&#xff0c;仅供参考&#xff0c;欢迎批评指正&#xff01; 另外&#xff0c;如果想了解更多ts相关知识&#xff0c;可以参考我的其他笔记&#xff1a; vue3ts开发干货笔记ts相关笔记&#xff08;基础必看&#xff09;ts相关笔记…

LeetCode-有效的字母异位词(242)

题目描述&#xff1a; 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t 互为字母异位词。 思路&#xff1a; 这题还是比较简单的&#xff0c;首先将两个字符…

盖子的c++小课堂——第二十三讲:背包问题

前言 又是一次漫长的更新&#xff08;我真不是故意的aaaaaaaaaaaaaaa&#xff09;&#xff0c;先不多说了&#xff0c;直接给我~坐下~说错了说错了&#xff0c;直接开始~ 背包问题----动态规划 背包问题&#xff08;knapsack problem&#xff09; 动态规划&#xff08;dyna…

基于python的leetcode算法介绍之动态规划

文章目录 零 算法介绍一 例题介绍 使用最小花费爬楼梯问题分析 Leetcode例题与思路[118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/)解题思路题解 [53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/)解题思路题解 [96. 不同的二叉搜索树](h…

企业出海数据合规:GDPR中的个人数据与非个人数据之区分

GDPR仅适用于个人数据&#xff0c;这意味着非个人数据不在其适用范围内。因此&#xff0c;个人数据的定义是一个至关重要的因素&#xff0c;因为它决定了处理数据的实体是否要遵守该法规对数据控制者规定的各种义务。尽管如此&#xff0c;什么是个人数据仍然是当前数据保护制度…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -小程序首页实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

Docker 镜像以及镜像分层

Docker 镜像以及镜像分层 1 什么是镜像2 Docker镜像加载原理2.1 UnionFs&#xff1a;联合文件系统2.2 Docker镜像加载原理2.3 Docker镜像的特点 3 镜像的分层结构4 可写的容器层 1 什么是镜像 镜像是一种轻量级、可执行的独立软件包&#xff0c;用来打包软件运行环境和基于运行…

数据处理四 基于图像hash进行数据整理(删除重复图片、基于模版查找图片)

一、背景知识 1.1 什么是hash Hash&#xff0c;一般翻译做“散列”&#xff0c;也有直接音译为“哈希”的&#xff0c;基本原理就是把任意长度的输入&#xff0c;通过Hash算法变成固定长度的输出。这个映射的规则就是对应的Hash算法&#xff0c;而原始数据映射后的二进制串就…

程序员必知!命令模式的实战应用与案例分析

命令模式是一种行为设计模式&#xff0c;它将请求封装为对象以实现客户端参数化、请求排队、日志记录及撤销操作&#xff0c;旨在解耦调用者与操作实现者&#xff0c;以智能家居为例&#xff0c;用户通过界面发送命令对象&#xff0c;设备作为接收者执行相应操作&#xff0c;无…

Ubuntu 安装Nginx服务

文章目录 前言一、Nginx安装1. Nginx默认安装2. Nginx指定版本安装3. Nginx验证4. Nginx服务控制4.1 查看服务状态4.2 停止服务4.3 启动服务4.4 重启服务 5. Nginx文件存放目录 二、自己编译Nginx1. 下载源码2. 依赖配置3. 编译 三、Nginx卸载总结 前言 Nginx&#xff08;发音为…

机器学习(四) -- 模型评估(3)

系列文章目录 机器学习&#xff08;一&#xff09; -- 概述 机器学习&#xff08;二&#xff09; -- 数据预处理&#xff08;1-3&#xff09; 机器学习&#xff08;三&#xff09; -- 特征工程&#xff08;1-2&#xff09; 机器学习&#xff08;四&#xff09; -- 模型评估…

探索 OceanBase 中图数据的实现

在数据管理和处理的现代环境中&#xff0c;对能够处理复杂数据结构的复杂数据模型和方法的需求从未如此迫切。图数据的出现以其自然直观地表示复杂关系的独特能力&#xff0c;开辟了数据分析的新领域。 虽然 Neo4j 等成熟的图形数据库为处理图形数据提供了强大的解决方案&…

如何理解Transformer论文中的positional encoding,和三角函数有什么关系?

大家好&#xff0c;我分享交流下这个问题。 Positional Encoding 掏出一张被无数人讲述的架构图。 Transformer 模型中的位置编码&#xff08;Positional Encoding&#xff09;是为了让模型能够考虑单词在句子中的位置。 由于 Transformer 的自注意力&#xff08;Self-Atte…

bat批处理文件_bat注释汇总

文章目录 1、示例&#xff08;直接结合脚本和结果进行理解&#xff09; 1、示例&#xff08;直接结合脚本和结果进行理解&#xff09; %这是一个注释% %这是另一个注释%rem 这是一个注释 rem 这是另一个注释:这是一个注释 ::这是一个注释 :?这是另一个注释if 1 1 ( %这里会执…

伐木工 - 华为OD统一考试

OD统一考试 题解&#xff1a; Java / Python / C 题目描述 一根X米长的树木&#xff0c;伐木工切割成不同长度的木材后进行交易&#xff0c;交易价格为每根木头长度的乘积。规定切割后的每根木头长度都为正整数,也可以不切割&#xff0c;直接拿整根树木进行交易。请问伐木工如…

libexif库介绍

libexif是一个用于解析、编辑和保存EXIF数据的库。它支持EXIF 2.1标准(以及2.2中的大多数)中描述的所有EXIF标签。它是用纯C语言编写的&#xff0c;不需要任何额外的库。源码地址&#xff1a;https://github.com/libexif/libexif &#xff0c;最新发布版本为0.6.24&#xff0c;…

深度学习中的自动化标签转换:对数据集所有标签做映射转换

在机器学习中&#xff0c;特别是在涉及图像识别或分类的项目中&#xff0c;标签数据的组织和准确性至关重要。本文探讨了一个旨在高效转换标签数据的 Python 脚本。该脚本在需要更新或更改类标签的场景中特别有用&#xff0c;这是正在进行的机器学习项目中的常见任务。我们将逐…