常见的排序算法(二)
归并排序
归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的排序算法。它将一个大的问题分解成小的问题,然后递归地解决这些小问题,最后合并(merge)得到最终的排序结果。归并排序的时间复杂度为 ( O(n \log n) ),它是一种稳定的排序算法。由于其稳定性和良好的最坏情况表现,归并排序在许多实际应用中都有着重要的地位。
一、归并排序的基本思想
归并排序的核心思想是将数组分成两个子数组,对这两个子数组分别进行排序,排序完成后再将它们合并成一个有序数组。归并排序的分治过程通常通过递归来实现:
- 分解:将数组分成两半。
- 解决:递归地对这两半数组分别进行归并排序。
- 合并:将两个有序的子数组合并成一个有序数组。
这种方法可以持续分解直到每个子数组只有一个元素,因为一个元素的数组默认是有序的。然后通过合并操作将这些有序的子数组组合成一个大的有序数组。
二、归并排序的具体步骤
1. 递归分解
首先,将一个大的数组分解成两个小数组,再递归地对这两个小数组进行归并排序,直到每个数组只有一个元素。
2. 合并操作
合并是归并排序的核心步骤。将两个已经排好序的数组合并成一个有序数组。对于每对元素,比较它们的大小,把较小的元素放入结果数组中,直到所有元素都合并完成。
3. 递归的终止条件
递归终止的条件是子数组的大小为1,此时该子数组已经是有序的,可以进行合并。
三、归并排序的时间复杂度分析
归并排序的时间复杂度为 ( O(n \log n) ),其中:
- 分解的次数:每次将数组分成两半,直到每个子数组只有一个元素。这个过程是递归的,深度为 ( \log n )。
- 合并的时间复杂度:每次合并操作需要遍历所有的元素,时间复杂度为 ( O(n) )。
因此,归并排序的总时间复杂度是 ( O(n \log n) )。
四、归并排序的空间复杂度分析
归并排序需要额外的空间来存储合并过程中生成的临时数组。每一次合并都需要额外的空间,因此空间复杂度为 ( O(n) )。
五、归并排序的特点
- 稳定性:归并排序是一个稳定的排序算法,即两个相等的元素在排序后相对位置不变。
- 时间复杂度:在最坏、最好、平均情况下,归并排序的时间复杂度都是 ( O(n \log n) )。
- 空间复杂度:归并排序需要额外的 ( O(n) ) 空间来存储临时数据。
- 适用场景:适用于大规模数据排序,尤其是当数据量很大时,归并排序表现非常稳定。特别适用于外部排序(比如磁盘上的数据排序)。
六、归并排序的C语言实现
下面是归并排序的 代码示例:
#include <stdio.h>// 合并两个子数组 arr[left..mid] 和 arr[mid+1..right]
void merge(int arr[], int left, int mid, int right) {int n1 = mid - left + 1; // 左子数组的长度int n2 = right - mid; // 右子数组的长度// 创建临时数组int leftArr[n1], rightArr[n2];// 将数据复制到临时数组for (int i = 0; i < n1; i++)leftArr[i] = arr[left + i];for (int i = 0; i < n2; i++)rightArr[i] = arr[mid + 1 + i];// 合并临时数组到原始数组int i = 0, j = 0, k = left;while (i < n1 && j < n2) {if (leftArr[i] <= rightArr[j]) {arr[k] = leftArr[i];i++;} else {arr[k] = rightArr[j];j++;}k++;}// 将剩余的元素复制到原数组while (i < n1) {arr[k] = leftArr[i];i++;k++;}while (j < n2) {arr[k] = rightArr[j];j++;k++;}
}// 归并排序的递归实现
void mergeSort(int arr[], int left, int right) {if (left < right) {int mid = left + (right - left) / 2; // 计算中间位置// 递归排序左半部分mergeSort(arr, left, mid);// 递归排序右半部分mergeSort(arr, mid + 1, right);// 合并已排序的子数组merge(arr, left, mid, right);}
}// 打印数组
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 arr_size = sizeof(arr) / sizeof(arr[0]);printf("原始数组: \n");printArray(arr, arr_size);mergeSort(arr, 0, arr_size - 1); // 调用归并排序printf("排序后的数组: \n");printArray(arr, arr_size);return 0;
}
代码解释:
merge
函数:合并两个已经排序的子数组。arr[left..mid]
和arr[mid+1..right]
,将它们合并成一个有序数组并放回原数组arr
中。mergeSort
函数:归并排序的递归实现,首先将数组分割成两个子数组,然后递归地对这两个子数组进行排序,最后合并它们。printArray
函数:打印数组,用于显示排序前后的数组。
测试结果:
原始数组: 12 11 13 5 6 7 排序后的数组: 5 6 7 11 12 13
七、归并排序的改进
尽管归并排序是一种非常有效的排序算法,但它的空间复杂度 ( O(n) ) 使得它在某些情况下表现不如其他排序算法。例如,对于小规模的数据,快速排序和堆排序可能会有更好的表现。为了优化归并排序的一些空间消耗,有人提出了优化版本:
-
原地归并排序:将归并过程进行修改,避免使用额外的数组来存储临时数据,减少空间开销。但这会使代码变得更加复杂。
-
优化合并过程:对于已经部分有序的数组,优化合并过程,减少不必要的操作。
小结:
归并排序作为一种稳定的、时间复杂度为 ( O(n \log n) ) 的排序算法,适用于大规模数据的排序。尽管它需要额外的空间,但其性能非常稳定,在最坏情况下也不会退化。归并排序尤其在外部排序中有重要应用,比如对磁盘中的大量数据进行排序。
堆排序
堆排序(Heap Sort)是一种利用堆(Heap)数据结构的排序算法。它的核心思想是通过构建最大堆或最小堆来排序。堆是一种完全二叉树,满足堆的性质,即每个节点的值都大于或小于其子节点的值。堆排序通过不断地调整堆的结构来实现排序。
一、堆的定义与性质
堆是一个完全二叉树,并且满足以下两个性质之一:
- 最大堆:对于树中的任意节点 ( i ),有 ( A[i] \geq A[2i+1] ) 和 ( A[i] \geq A[2i+2] ),即父节点的值大于或等于子节点的值。
- 最小堆:对于树中的任意节点 ( i ),有 ( A[i] \leq A[2i+1] ) 和 ( A[i] \leq A[2i+2] ),即父节点的值小于或等于子节点的值。
二、堆排序的基本步骤
堆排序的过程可以分为两大部分:
- 构建堆:将无序数组构建成一个堆。堆可以是最大堆或最小堆,通常我们使用最大堆来实现升序排序。
- 堆调整:将堆顶元素(最大值)与堆的最后一个元素交换,然后减少堆的大小(忽略最后一个元素),重新调整堆,使其恢复堆的性质。重复这个过程直到堆的大小为1。
堆排序的时间复杂度为 ( O(n \log n) ),其中 ( n ) 是待排序数组的元素个数。
三、 堆排序的工作原理
构建最大堆:
- 从最后一个非叶子节点开始,逐个向上调整每个节点的位置,使其满足最大堆的性质。
- 调整过程涉及比较父节点与子节点的值,若父节点小于任何一个子节点,就交换它们的位置,并递归地对交换后的子树进行调整。
堆排序的具体过程:
- 构建最大堆:从数组的最后一个非叶子节点开始,调整堆,直到根节点。
- 交换根节点与最后一个节点:将堆顶元素(最大元素)与数组最后一个元素交换。
- 减少堆的大小:忽略最后一个元素(它已经是排好序的),调整剩下的元素,使其重新成为最大堆。
- 重复上述步骤:直到堆中只剩下一个元素为止。
四、 堆排序的代码实现
接下来,通过C语言实现堆排序的具体过程。我们首先需要实现最大堆的调整操作,然后通过交换堆顶元素与堆的最后一个元素来实现排序。
#include <stdio.h>// 调整堆的函数,确保根节点满足最大堆性质
void heapify(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) {int temp = arr[i];arr[i] = arr[largest];arr[largest] = temp;// 递归地调整被交换位置的子树heapify(arr, n, largest);}
}// 堆排序的主函数
void heapSort(int arr[], int n) {// 构建最大堆for (int i = n / 2 - 1; i >= 0; i--) {heapify(arr, n, i);}// 逐步将最大元素放到数组的末尾for (int i = n - 1; i >= 1; i--) {// 交换根节点(最大值)与当前最后一个元素int temp = arr[0];arr[0] = arr[i];arr[i] = temp;// 调整堆的大小heapify(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("原始数组:\n");printArray(arr, n);heapSort(arr, n);printf("排序后的数组:\n");printArray(arr, n);return 0;
}
代码解析:
-
heapify函数:该函数用于调整堆的性质。它接收数组
arr
,数组大小n
,和当前节点的索引i
。通过比较当前节点与左右子节点的值,决定是否交换它们,并递归地调整子树,直到整个子树满足堆的性质。 -
heapSort函数:该函数实现堆排序的主逻辑。首先通过
heapify
构建一个最大堆,然后将堆顶的最大元素与堆的最后一个元素交换,减少堆的大小,再次调用heapify
调整堆。重复这一过程,直到所有元素都被排好序。 -
printArray函数:打印数组,用于查看排序前后的结果。
-
main函数:主函数中,我们定义了一个待排序的数组,调用堆排序函数,并输出排序结果。
五、堆排序的时间复杂度分析
堆排序的时间复杂度是 ( O(n \log n) ),下面是详细分析:
-
构建最大堆:从最后一个非叶子节点开始,调整堆。调整每个节点的时间复杂度是 ( O(\log n) ),总的构建堆的时间复杂度是 ( O(n) )。
-
交换与堆调整:在堆排序过程中,每次将堆顶元素交换到数组末尾,然后减少堆的大小并调整堆。每次调整堆的时间复杂度是 ( O(\log n) ),总共需要进行 ( n-1 ) 次交换,因此总体时间复杂度是 ( O(n \log n) )。
综上所述,堆排序的时间复杂度为 ( O(n \log n) )。
六、 堆排序的空间复杂度
堆排序的空间复杂度是 ( O(1) ),因为它是原地排序算法,不需要额外的空间来存储数据,只需要常数空间来存储一些辅助变量。
七、堆排序的优缺点
优点:
- 时间复杂度稳定:无论数据的初始状态如何,堆排序的时间复杂度始终是 ( O(n \log n) ),不像快速排序那样最坏情况下退化到 ( O(n^2) )。
- 原地排序:堆排序不需要额外的存储空间,只需要常数空间。
缺点:
- 不稳定排序:堆排序是一个不稳定的排序算法,即相等的元素可能会改变相对顺序。
- 常数因子较大:与快速排序相比,堆排序常数因子较大,通常在实际应用中速度较慢。
八、总结
堆排序是一种基于堆数据结构的高效排序算法,它通过构建最大堆(或最小堆)来实现排序。堆排序具有 ( O(n \log n) ) 的时间复杂度,并且是原地排序算法,不需要额外的空间。然而,堆排序的主要缺点是它是一种不稳定排序,因此在一些需要稳定排序的场合,可能需要选择其他排序算法。
相关文章:

常见的排序算法(二)
归并排序 归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的排序算法。它将一个大的问题分解成小的问题,然后递归地解决这些小问题,最后合并(merge)得到最终的排序结果。…...

spark的RDD分区的设定规则
目录 一、第一种:parallelize 获取rdd时 二、第二种:通过外部读取数据-textFile 三、上面提到了默认分区数,那么默认分区是怎么计算呢? 一、第一种:parallelize 获取rdd时 没有指定:spark.default.paral…...

【点云网络】voxelnet 和 pointpillar
VoxelNet 和 pointpillar 这两个网络可以认为后者是前者的升级版本,都是采用了空间划分的方法, 一个是体素,一个是pillar, 前者是3D卷积处理中间特征,后者是2D卷积处理中间特征。 voxelnet voxelnet 应该是比较早的onestage的网…...

HAL库硬件IIC驱动气压传感器BMP180
环境 1、keilMDK 5.38 2、STM32CUBEMX 初始配置 默认即可。 程序 1、头文件 #ifndef __BMP_180_H #define __BMP_180_H#include "main.h"typedef struct {float fTemp; /*温度,摄氏度*/float fPressure; /*压力,pa*/float fAltitude; /*…...

探索Python音频处理的奥秘:Pydub库的魔法
文章目录 探索Python音频处理的奥秘:Pydub库的魔法第一部分:背景介绍第二部分:Pydub是什么?第三部分:如何安装Pydub?第四部分:Pydub的简单函数使用方法1. 打开音频文件2. 播放音频3. 导出音频文…...

LeetCode 热题100(七)【链表】(2)
目录 7.6合并两个有序链表(简单) 7.7两数相加(中等) 7.8删除链表的倒数第N个节点(中等) 7.9两两交换链表中的节点(中等) 7.10k个一组翻转链表(困难) 7.6…...

计算机网络 TCP/IP体系 网络层
一. 网络层的基本概念 网络层主要负责将数据从源端主机发送到目的端主机。在这一过程中,网络层要解决的关键问题是数据包的路由选择,即确定数据包通过互联网的最佳路径。 1.1 网络层的信息类型 数据包:这是网络层传输的主要形式,…...

迈入国际舞台,AORO M8防爆手机获国际IECEx、欧盟ATEX防爆认证
近日,深圳市遨游通讯设备有限公司(以下简称“遨游通讯”)旗下5G防爆手机——AORO M8,通过了CSA集团的严格测试和评估,荣获国际IECEx及欧盟ATEX防爆认证证书。2024年11月5日,CSA集团和遨游通讯双方领导在遨游…...

实习作假:阿里健康实习做了RABC中台,还优化了短信发送流程
最近有二本同学说:“大拿老师,能帮忙看下简历吗?” 如果是从面试官的角度来看,这个同学的实习简历是很虚假的。 但是我们一直强调的是:校招的实习简历是不能出现明显的虚假。 首先,你去公司做事情&#…...

Unity中IK动画与布偶死亡动画切换的实现
在Unity游戏开发中,Inverse Kinematics(IK)是创建逼真角色动画的强大工具。同时,能够在适当的时候切换到布偶物理状态来实现死亡动画等效果,可以极大地增强游戏的视觉体验。本文将详细介绍如何在Unity中利用IK实现常规…...

java导出word文件(手绘)
文章目录 代码细节效果图参考资料 代码细节 使用的hutool的WordUtil,WordUtil对poi进行封装,但是这一块的官方封装的很少,很多细节都没有。代码中是常见的绘制段落,标题、表格等常用api Word07Writer writer WordUtil.getWriter(…...

ssm070基于SSM框架的校园代购服务订单管理系统的设计与实现+vue(论文+源码)_kaic
毕业设计 题 目: 校园代购服务订单管理系统 作 者: 学 号: 所属学院: 专业年级: 学校导师: 职 称: 班级导师: 职 称: 完成时间…...

Java项目实战II基于Spring Boot的秒杀系统设计与实现(开发文档+数据库+源码)
目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 在互联网电商蓬勃发展的今天࿰…...

FastAPI —— 请求参数验证
1.hello world 给后端船数据 hello world 接口给后端传 COVID-19 感染数据_高性能 FastAPI 框架入门精讲-慕课网 #!/usr/bin/python3 # -*- coding:utf-8 -*- # __author__ __Jack__from typing import Optionalfrom fastapi import FastAPI from pydantic import BaseModel…...

第七篇: BigQuery中的复杂SQL查询
BigQuery中的复杂SQL查询 背景与目标 在数据分析中,我们通常需要从多个数据源中获取信息,以便进行深入的分析。这时,BigQuery提供的JOIN、UNION和子查询等复杂SQL语句非常实用。本文将以Google BigQuery的公共数据集为例,介绍如何…...

【SQL实验】高级查询(难点.三)含附加数据库操作
完整代码在文章末尾【代码是自己的解答,并非标准答案,也有可能写错,文中可能会有不准确或待完善之处,恳请各位读者不吝批评指正,共同促进学习交流】 将素材中的“学生管理”数据库附加到SQL SERVER中,完成以…...

qt QFileSystemModel详解
1、概述 QFileSystemModel是Qt框架中的一个关键类,它继承自QAbstractItemModel,专门用于在Qt应用程序中展示文件系统的数据。这个模型提供了一个方便的接口,使得开发者可以轻松地在应用程序中集成文件和目录的树形结构,并通过视图…...

element plus中修改el-table的样式
文章目录 前情提要相关环境package.jsonvue代码结果 方式一直接看代码 方式二直接看代码 前情提要 因为项目中用到el-table的时候,需要将el-table表格的样式进行修改,将整个表格的背景颜色从白色变成透明,使得表格变得透明之后,展…...

深入理解封装与接口:Java程序设计的核心思想与最佳实践
目录 一、封装的优点 二、接口与默认方法 三、总结 在面向对象编程(OOP)中,封装(Encapsulation)是一个核心概念,Java对其进行了良好的支持。封装不仅有助于提高代码的安全性,还能够增强代码的…...

linux 下调试 mpu6050 三轴加速度
供自己备忘; 1. 参考资料: b 站视频 https://www.bilibili.com/video/BV1cL4y1x7FA/?spm_id_from333.337.search-card.all.click&vd_sourced7a07b8689c9e646f0214227c06f304c csdn 其它博客 https://blog.csdn.net/qq_65198598/article/detail…...

C语言心型代码解析
方法一 心型极坐标方程 爱心代码你真的理解吗 笛卡尔的心型公式: for (y 1.5; y > -1.5; y - 0.1) for (x -1.5; x < 1.5; x 0.05) 代码里面用了二个for循环,第一个代表y轴,第二个代表x轴 二个增加的单位不同,能使得…...

【LeetCode】【算法】647. 回文子串
LeetCode 647.回文子串 题目描述 给你一个字符串s,请你统计并返回这个字符串中回文子串的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串是字符串中的由连续字符组成的一个序列。 思路 思路:中心拓展法 中心拓展法的意思是说…...

介绍6种常见的基于知识图谱推荐算法的类型和各自的优缺点
基于知识图谱的推荐算法有多种,每种算法各有其优点和缺点。下面是一些常见的基于知识图谱的推荐算法及其分析: 基于邻域的协同过滤(Collaborative Filtering) 描述:通过分析用户之间的相似性或项目之间的相似性来进行…...

使用python拟合二元曲线系数
python import numpy as np import pandas as pd注: xlsx 表格中 有 压力P,流量值Q,温度值 K; df pd.read_excel("./i100-10000slm.xlsx",usecols[p1,molboxQm,Dek]) #print(df.head())#column_data df[p1] # 获取行数…...

go 集成viper 配置管理
安装viper go get github.com/spf13/viper 配置文件 读取配置文件 package confimport ("fmt""github.com/spf13/viper" )func Properties() {viper.SetConfigName("application")viper.SetConfigType("yml")viper.AddConfigPath(&q…...

Java:数据结构-再谈String类
字符串常量池 首先我们来思考这段代码,为什么运行结果一个是true,一个是false呢? public class Test {public static void main(String[] args) {String s1"123";String s2"123";String s3new String("555")…...

斗破QT编程入门系列之二:GUI应用程序设计基础:UI文件(四星斗师)
斗破Qt目录: 斗破Qt编程入门系列之前言:认识Qt:Qt的获取与安装(四星斗师) 斗破QT编程入门系列之一:认识Qt:初步使用(四星斗师) 斗破QT编程入门系列之二:认识…...

微服务实战系列之玩转Docker(十八)
导览 前言Q:如何保障容器云环境下etcd集群的数据安全一、安全机制身份认证必学必看1. 启动参数2. 授权命令3. 开启认证 二、应用实践1. 访问容器2. 查看认证是否开启3. 查看是否已创建用户4. 创建用户5. 开启认证6. 验证是否开启7. 验证数据 结语系列回顾 前言 etc…...

Javascript 获取设备信息 工具
JS获取设备信息(操作系统信息、地理位置、UUID、横竖屏状态、设备类型、网络状态、浏览器信息、生成浏览器指纹、日期、生肖、周几等) Get Device Info Online GitHub - skillnull/DeviceJs: JS获取设备信息(操作系统信息、地理位置、UUID、横竖屏状态、设备类型、网络状态、浏…...

基于开源 AI 智能名片、S2B2C 商城小程序的用户获取成本优化分析
摘要:本文围绕用户获取成本(CAC)这一关键指标展开深入剖析,详细阐述其计算方式,并紧密结合开源 AI 智能名片与 S2B2C 商城小程序的独特性质,从多个维度探讨如何通过挖掘新的获客渠道、巧妙运用私域流量池等…...