常见的排序算法(二)

归并排序

归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的排序算法。它将一个大的问题分解成小的问题,然后递归地解决这些小问题,最后合并(merge)得到最终的排序结果。归并排序的时间复杂度为 ( O(n \log n) ),它是一种稳定的排序算法。由于其稳定性和良好的最坏情况表现,归并排序在许多实际应用中都有着重要的地位。

一、归并排序的基本思想

归并排序的核心思想是将数组分成两个子数组,对这两个子数组分别进行排序,排序完成后再将它们合并成一个有序数组。归并排序的分治过程通常通过递归来实现:

  1. 分解:将数组分成两半。
  2. 解决:递归地对这两半数组分别进行归并排序。
  3. 合并:将两个有序的子数组合并成一个有序数组。

这种方法可以持续分解直到每个子数组只有一个元素,因为一个元素的数组默认是有序的。然后通过合并操作将这些有序的子数组组合成一个大的有序数组。

二、归并排序的具体步骤

1. 递归分解

首先,将一个大的数组分解成两个小数组,再递归地对这两个小数组进行归并排序,直到每个数组只有一个元素。

2. 合并操作

合并是归并排序的核心步骤。将两个已经排好序的数组合并成一个有序数组。对于每对元素,比较它们的大小,把较小的元素放入结果数组中,直到所有元素都合并完成。

3. 递归的终止条件

递归终止的条件是子数组的大小为1,此时该子数组已经是有序的,可以进行合并。

三、归并排序的时间复杂度分析

归并排序的时间复杂度为 ( O(n \log n) ),其中:

  • 分解的次数:每次将数组分成两半,直到每个子数组只有一个元素。这个过程是递归的,深度为 ( \log n )。
  • 合并的时间复杂度:每次合并操作需要遍历所有的元素,时间复杂度为 ( O(n) )。

因此,归并排序的总时间复杂度是 ( O(n \log n) )。

四、归并排序的空间复杂度分析

归并排序需要额外的空间来存储合并过程中生成的临时数组。每一次合并都需要额外的空间,因此空间复杂度为 ( O(n) )。

五、归并排序的特点

  1. 稳定性:归并排序是一个稳定的排序算法,即两个相等的元素在排序后相对位置不变。
  2. 时间复杂度:在最坏、最好、平均情况下,归并排序的时间复杂度都是 ( O(n \log n) )。
  3. 空间复杂度:归并排序需要额外的 ( O(n) ) 空间来存储临时数据。
  4. 适用场景:适用于大规模数据排序,尤其是当数据量很大时,归并排序表现非常稳定。特别适用于外部排序(比如磁盘上的数据排序)。

六、归并排序的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;
}

代码解释:

  1. merge函数:合并两个已经排序的子数组。arr[left..mid] 和 arr[mid+1..right],将它们合并成一个有序数组并放回原数组 arr 中。
  2. mergeSort函数:归并排序的递归实现,首先将数组分割成两个子数组,然后递归地对这两个子数组进行排序,最后合并它们。
  3. printArray函数:打印数组,用于显示排序前后的数组。

测试结果:

原始数组: 12 11 13 5 6 7 排序后的数组: 5 6 7 11 12 13

七、归并排序的改进

尽管归并排序是一种非常有效的排序算法,但它的空间复杂度 ( O(n) ) 使得它在某些情况下表现不如其他排序算法。例如,对于小规模的数据,快速排序和堆排序可能会有更好的表现。为了优化归并排序的一些空间消耗,有人提出了优化版本:

  1. 原地归并排序:将归并过程进行修改,避免使用额外的数组来存储临时数据,减少空间开销。但这会使代码变得更加复杂。

  2. 优化合并过程:对于已经部分有序的数组,优化合并过程,减少不必要的操作。

小结:

归并排序作为一种稳定的、时间复杂度为 ( 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. 构建堆:将无序数组构建成一个堆。堆可以是最大堆或最小堆,通常我们使用最大堆来实现升序排序。
  2. 堆调整:将堆顶元素(最大值)与堆的最后一个元素交换,然后减少堆的大小(忽略最后一个元素),重新调整堆,使其恢复堆的性质。重复这个过程直到堆的大小为1。

堆排序的时间复杂度为 ( O(n \log n) ),其中 ( n ) 是待排序数组的元素个数。

三、 堆排序的工作原理

构建最大堆:
  1. 从最后一个非叶子节点开始,逐个向上调整每个节点的位置,使其满足最大堆的性质。
  2. 调整过程涉及比较父节点与子节点的值,若父节点小于任何一个子节点,就交换它们的位置,并递归地对交换后的子树进行调整。
堆排序的具体过程:
  1. 构建最大堆:从数组的最后一个非叶子节点开始,调整堆,直到根节点。
  2. 交换根节点与最后一个节点:将堆顶元素(最大元素)与数组最后一个元素交换。
  3. 减少堆的大小:忽略最后一个元素(它已经是排好序的),调整剩下的元素,使其重新成为最大堆。
  4. 重复上述步骤:直到堆中只剩下一个元素为止。

四、 堆排序的代码实现

接下来,通过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;
}
代码解析:
  1. heapify函数:该函数用于调整堆的性质。它接收数组 arr,数组大小 n,和当前节点的索引 i。通过比较当前节点与左右子节点的值,决定是否交换它们,并递归地调整子树,直到整个子树满足堆的性质。

  2. heapSort函数:该函数实现堆排序的主逻辑。首先通过 heapify 构建一个最大堆,然后将堆顶的最大元素与堆的最后一个元素交换,减少堆的大小,再次调用 heapify 调整堆。重复这一过程,直到所有元素都被排好序。

  3. printArray函数:打印数组,用于查看排序前后的结果。

  4. 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) ) 的时间复杂度,并且是原地排序算法,不需要额外的空间。然而,堆排序的主要缺点是它是一种不稳定排序,因此在一些需要稳定排序的场合,可能需要选择其他排序算法。

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

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

相关文章

基于matlab的人眼开度识别

我国已经成为世界汽车生产和制造大国&#xff0c;道路车辆的不断增加道路基础设施不断增强&#xff0c;但是随之而来的问题也日益严重&#xff0c;比如交通事故&#xff0c;噪声大气污染等。汽车行驶的安全性由于关乎人民生命安全&#xff0c;所以日益受到各国政府以及研究机构…

使用 Spring Boot 搭建 WebSocket 服务器实现多客户端连接

在 Web 开发中&#xff0c;WebSocket 为客户端和服务端之间提供了实时双向通信的能力。本篇博客介绍如何使用 Spring Boot 快速搭建一个 WebSocket 服务器&#xff0c;并支持多客户端的连接和消息广播。 1. WebSocket 简介 WebSocket 是 HTML5 的一种协议&#xff0c;提供了客…

详细分析Java8中的Duration时间类基本知识(附Demo)

目录 前言1. 基本知识2. Demo3. 拓展Demo 前言 基本的Java知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;【Java项目】实战CRUD的功能整理&#xff08;持续更新&#xff09; 1. 基本知识 Duration 类是 Ja…

Day14买卖股票的最佳时机

给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。如…

【星闪EBM-H63开发板】小熊派的开发板实物

前几天介绍了&#xff0c;我参加了小熊派的星闪EBM-H63开发板内测活动。今天去快递站取回来开发板&#xff0c;让我们看看实物照片。 下面是开发板的正面照片&#xff0c;绿色的部分是核心板&#xff0c;蓝色的部分实际上是个底板&#xff0c;引出了串口的管脚&#xff0c;再加…

zookeeper的选举机制

zk中有两种角色&#xff1a;Leader 和 Fllower Leader是自己的集群各台电脑投票选举出来的。 事务&#xff1a;一通操作&#xff0c;要么同时成立&#xff0c;要么都不成立。 举例&#xff1a;Jack 和 Rose Rose 给 Jack&#xff08;小李子&#xff09; 转钱 Rose -100 J…

六:java 基础知识(6)-- Maven项目管理

目录 1. 引言 1.1 什么是 Maven 1.2 Maven 的优势 2. Maven 基础 2.1 Maven 的基本概念 2.2 Maven 的工作原理 2.3 Maven 的安装与配置 3. Maven 项目结构 3.1 Maven 项目的标准目录结构 3.2 POM 文件&#xff08;pom.xml&#xff09;概述 3.3 POM 文件的基本配置 4…

Centrifuge 5425R - 微量离心机技术数据详情——艾本德

离心机&#xff0c;作为实验室中的关键设备&#xff0c;扮演着分离、浓缩和纯化样品的重要角色。而在众多离心机型号中&#xff0c;Centrifuge 5425R 以其出色的性能和多功能性&#xff0c;成为了众多科研人员的首选。下面&#xff0c;我们就来详细了解一下这款微量离心机的技术…

【06】A-Maven项目SVN设置忽略文件

做Web项目开发时&#xff0c;运用的是Maven管理工具对项目进行管理&#xff0c;在项目构建的过程中自动生成了很多不需要SVN进行管理的文件&#xff0c;SVN在对源码进行版本管理时&#xff0c;需要将其忽略&#xff0c;本文给出了具体解决方案。 SVN设置忽略Maven项目中自动生成…

TDengine 签约蘑菇物联,改造通用设备工业互联网平台

在当前工业互联网迅猛发展的背景下&#xff0c;企业面临着日益增长的数据处理需求和智能化转型的挑战。通用工业设备的高能耗问题愈发突出&#xff0c;尤其是由这些设备组成的公辅能源车间&#xff0c;亟需更高效的解决方案来提升设备运行效率&#xff0c;降低能源消耗。为此&a…

革新汽车装配产线:MR30分布式IO模块引领智能制造新时代

在日新月异的汽车制造行业中&#xff0c;每一分每一秒的效率提升都意味着成本的降低与市场竞争力的增强。随着工业4.0时代的到来&#xff0c;智能化、自动化已成为汽车产线升级转型的关键词。在这场技术革命的浪潮中&#xff0c;MR30分布式IO模块以其高效、灵活、可靠的特点&am…

Qt第三课 ----------容器类控件

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

CodeQL学习笔记(4)-CodeQL for Java(程序元素)

最近在学习CodeQL&#xff0c;对于CodeQL就不介绍了&#xff0c;目前网上一搜一大把。本系列是学习CodeQL的个人学习笔记&#xff0c;根据个人知识库笔记修改整理而来的&#xff0c;分享出来共同学习。个人觉得QL的语法比较反人类&#xff0c;至少与目前主流的这些OOP语言相比&…

动态规划28:376. 摆动序列

动态规划解题步骤&#xff1a; 1.确定状态表示&#xff1a;dp[i]是什么 2.确定状态转移方程&#xff1a;dp[i]等于什么 3.初始化&#xff1a;确保状态转移方程不越界 4.确定填表顺序&#xff1a;根据状态转移方程即可确定填表顺序 5.确定返回值 题目链接&#xff1a;376.…

【zlm】h264 vp9 尝试研究

目录 编译与使用libvpx 打包lib 解决方案一 libvpx直接引用 IVF格式 编译libvpx windows下编译libvpx 参考文章 编译与使用libvpx 我们用最新的&#xff1a; x86_64-win64-vs16 最简单的视频编码器&#xff1a;编译&#xff08;libx264&#xff0c;libx265&#xff…

顺序表专题

目录 0. 什么是数据结构&#xff1f; 0. 为什么需要数据结构&#xff1f; 1.顺序表的概念及结构 2.顺序表分类&#xff1a; 3.动态顺序表的实现 4. 顺序表的应用 5. 顺序表的问题及思考 0. 什么是数据结构&#xff1f; 数据结构是由“数据”和“结构”两词结合而来 什…

关于使用svgIcon 菜单折叠 显示文字情况

使用的工具&#xff1a;vue2&#xff0c;ant design vue 问题&#xff1a; **解决&#xff1a;在<svg-icon> 外面包一层 <a-icon> ** 使用: 在 main.js 中&#xff1a;

【JAVA毕业设计】基于Vue和SpringBoot的师生健康管理系统

博主说明&#xff1a;本文项目编号 T 052 &#xff0c;文末自助获取源码 \color{red}{T052&#xff0c;文末自助获取源码} T052&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

双向链表专题

双向链表 1. 双向链表的定义和结构2. 双向链表的实现2.1 结构声明2.2 双向链表的初始化2.3 双向链表的打印2.4 尾插2.5 头插2.6 在指定位置之前插入2.7 在指定位置之后插入数据2.8 尾删2.9 头删2.10 删除指定位置的节点2.11 查找2.12 链表的销毁 3. 双向链表的细节 &#x1f52…

发票真伪查验方式-python数电票批量查验接口、发票ocr文字识别提取

在当今的商业环境中&#xff0c;确保交易的安全性和透明度是每个企业追求的目标。随着电子商务的迅猛发展&#xff0c;发票管理成为了企业财务管理中不可或缺的一环。面对海量的电子发票&#xff0c;企业财务也无需惊慌&#xff0c;翔云发票查验API接口&#xff0c;可以为企业提…