【八大排序】归并排序 | 计数排序 + 图文详解!!

在这里插入图片描述

📷 江池俊: 个人主页
🔥个人专栏: ✅数据结构冒险记 ✅C语言进阶之路
🌅 有航道的人,再渺小也不会迷途。


在这里插入图片描述

文章目录

    • 一、归并排序
      • 1.1 基本思想 + 动图演示
      • 2.2 递归版本代码实现 + 算法步骤
      • 2.3 非递归版本代码实现 + 算法步骤
      • 2.4 归并排序的特性总结
    • 二、计数排序
      • 2.1 基本思想
      • 2.2 动图演示
      • 2.3 算法步骤
      • 2.4 代码实现
      • 2.5 计数排序特性总结
    • 三、排序算法复杂度及稳定性分析

在这里插入图片描述

一、归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

1.1 基本思想 + 动图演示

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。它将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

在这里插入图片描述

在这里插入图片描述

2.2 递归版本代码实现 + 算法步骤

归并排序的基本思想是分治思想,它包括以下三个步骤:

  1. 分解(Divide):将含有n个元素的序列分成两个各自包含大约n/2个元素的子序列。(当数组分解成一个时即可认为其有序)
  2. 解决(Conquer):递归地对这两个子序列进行归并排序。
  3. 合并(Combine):将两个排序好的子序列合并成一个最终的排序序列。

归并排序通过不断地将大问题分解成小问题来解决,即把大的数组拆分成若干个小的数组,然后逐一合并这些有序的小数组来得到最终排序好的整体数组。这种算法非常适用于链表等数据结构,在处理大规模数据时尤其高效。

// 归并排序递归函数
void _MergeSort(int* a, int begin, int end, int* temp)
{if (begin >= end)return;int mid = (begin + end) / 2;// [begin, mid] [mid+1, end]_MergeSort(a, begin, mid, temp);_MergeSort(a, mid+1, end, temp);// ... 归并 [begin,mid] [mid+1,end]int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){temp[i++] = a[begin1++];}else{temp[i++] = a[begin2++];}}while (begin1 <= end1){temp[i++] = a[begin1++];}while (begin2 <= end2){temp[i++] = a[begin2++];}// 拷贝回原数组memcpy(a + begin, temp + begin, sizeof(int) * (end - begin + 1));
}// 归并排序
void MergeSort(int* a, int n)
{// 申请一个与原数组同样大小的空间int* temp = (int*)malloc(sizeof(int) * n);if (temp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n - 1, temp);free(temp);
}

【递归展开图】:
在这里插入图片描述

现在我们来分析一下以上代码:
这段代码是归并排序(Merge Sort)的实现。归并排序是一种分治算法,它将一个数组分成两半,对每一半进行排序,然后将两个有序的部分合并成一个有序的数组。以下是这段代码的算法思想和步骤分析:

  1. 递归划分

    • _MergeSort函数中,首先检查基准条件,即如果begin大于或等于end,则数组已经完全有序,所以直接返回。
    • 然后,计算中间索引mid,将数组分成两个子数组:[begin, mid][mid+1, end]
    • 对这两个子数组递归地进行归并排序。
  2. 合并

    • 在递归调用返回后,两个子数组都是有序的。然后,将这两个有序的子数组合并成一个有序的数组。
    • 合并操作通过双指针技术完成。指针begin1begin2分别指向两个子数组的开始位置,而指针end1end2分别指向两个子数组的结束位置。
    • 开始时,从两个子数组中取最小的元素,放到临时数组temp中,直到其中一个子数组被完全取完。
    • 然后,将剩余的子数组的所有元素复制到临时数组中。
  3. 拷贝回原数组

    • 最后,使用memcpy函数将临时数组中的元素复制回原数组。这一步是必要的,因为临时数组是在堆上分配的,而原数组是在栈上。
  4. 主函数

    • MergeSort函数是归并排序的入口点。它首先在堆上为原数组分配一个同样大小的临时数组。如果分配失败(即malloc返回NULL),则输出错误信息并返回。
    • 然后,调用递归函数_MergeSort对原数组进行排序。
    • 最后,释放临时数组以防止内存泄漏。
  5. 稳定性

    • 归并排序是稳定的排序算法,这意味着相等的元素在排序后保持其原始顺序。这是因为归并排序在合并两个子数组时,总是选择较小的元素,而不改变其相对顺序。
  6. 时间复杂度

    • 归并排序的时间复杂度为O(nlogn),其中n是数组的大小。这是因为每次递归调用都会将问题规模减半(logn),并且需要进行n次这样的递归调用(n)
  7. 空间复杂度

    • 归并排序的空间复杂度为O(n),因为需要一个与原数组同样大小的临时数组来存储合并过程中的中间结果。

2.3 非递归版本代码实现 + 算法步骤

// 归并排序(非递归)
void MergeSortNonR(int* a, int n)
{int* temp = (int*)malloc(sizeof(int) * n);if (temp == NULL){perror("malloc fail");return;}int gap = 1; // 通过gap来控制归并的两个区间的大小,表示的是这两个区间的大小while (gap < n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;// [begin1, end1] [begin2, end2] 归并// 边界处理if (end1 >= n || begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}// 归并int j = begin1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){temp[j++] = a[begin1++];}else{temp[j++] = a[begin2++];}}while (begin1 <= end1){temp[j++] = a[begin1++];}while (begin2 <= end2){temp[j++] = a[begin2++];}// 拷贝回原数组(边归并边拷贝) --- 因为最后可能有一个区间不需要归并,所以这一个区间的元素是不需要改变的,即不需要拷贝回去,若一次性拷贝回原数组,会使这个区间的元素全部变为随机值memcpy(a + i, temp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}free(temp);
}

对于上述代码我们接着来分析一下它的算法步骤:

【算法步骤】:

  1. 初始化

    • 定义一个临时数组temp,其大小为输入数组a的大小。
    • 初始化一个变量gap为1,它表示每次归并时每组数据的个数。
  2. 归并循环

    • gap小于输入数组的长度n时,进入循环。
    • 在每次循环中,将数组分为两个子数组(每个子数组的大小为gap),并对这两个子数组进行归并。
  3. 子数组归并

    • 定义两个子数组的起始和结束索引:begin1end1begin2end2
    • 处理边界情况:如果其中一个子数组超出数组范围,则退出循环。
    • 使用一个临时数组temp来存储归并的结果。
    • 使用一个双指针方法(类似于两个指针比较和交换)来将两个子数组合并为一个有序数组。
  4. 拷贝回原数组

    • 使用memcpy函数将临时数组中的数据拷贝回原数组。这一步是为了在归并过程中更新原数组。
  5. 扩大gap

    • 在每次循环结束时,将gap乘以2,以便在下一次循环中处理更大的子数组。
  6. 释放内存

    • 归并完成后,释放临时数组temp的内存。

2.4 归并排序的特性总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

二、计数排序

2.1 基本思想

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

在这里插入图片描述

2.2 动图演示

在这里插入图片描述

2.3 算法步骤

这段代码是实现计数排序算法的C语言代码。以下是该代码的算法步骤和思想分析:

算法步骤:

  1. 找出数组中的最小值和最大值:这是计数排序的一个重要步骤,因为算法需要对数组中的每个元素进行计数,所以需要知道元素的可能范围。
  2. 计算范围:根据最小值和最大值计算出元素的可能范围。
  3. 计数:遍历输入数组,对每个元素在其可能的范围内进行计数。
  4. 构建输出数组:根据计数结果,将每个元素放到它在输出数组中的正确位置。

2.4 代码实现

// 计数排序 
// 时间复杂度:O(N+range) 空间复杂度:O(range) 
void CountSort(int* a, int n)
{int min = a[0], max = a[0];for (int i = 1; i < n; i++){if (a[i] < min){min = a[i];}if (a[i] > max){max = a[i];}}int range = max - min + 1;int* count = (int*)calloc(range, sizeof(int));if (count == NULL){perror("calloc fail");return;}// 统计次数for (int i = 0; i < n; i++){count[a[i] - min]++;}// 排序int i = 0;for (int j = 0; j < range; j++){while (count[j]--){a[i++] = j + min;}}
}

2.5 计数排序特性总结

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。数排序适用于整数且范围较小的情况。对于范围较大的整数或小数,需要更复杂的排序算法。
  2. 时间复杂度:O(MAX(N,范围)),由于算法只涉及到一次遍历输入数组和一次遍历计数数组,所以时间复杂度为O(MAX(N,范围))
  3. 空间复杂度:O(范围),由于需要创建一个与范围大小相等的计数数组,所以空间复杂度为O(范围)
  4. 稳定性:稳定(相等的元素在排序后保持其原始顺序)

三、排序算法复杂度及稳定性分析

在这里插入图片描述
在这里插入图片描述


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

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

相关文章

Linux apmd命令教程:管理和监控电源管理功能(附案例详解和注意事项)

Linux apmd命令介绍 apmd 是 Advanced Power Management BIOS daemon 的缩写&#xff0c;它是一个用于管理和监控电源管理功能的守护进程。apmd 负责 BIOS 进阶电源管理 (APM) 相关的记录&#xff0c;警告与管理工作。 Linux apmd命令适用的Linux版本 apmd 命令在大多数 Lin…

containerd中文翻译系列(二十)快照器

快照器管理容器文件系统的快照。 可通过运行 ctr plugins ls 或 nerdctl info 查看可用的快照器。 核心快照器插件 通用&#xff1a; overlayfs&#xff08;默认&#xff09;&#xff1a; OverlayFS. 该驱动程序类似于 Docker/Moby 的 "overlay2 "存储驱动程序&a…

机器学习:回归决策树(Python)

一、平方误差的计算 square_error_utils.py import numpy as npclass SquareErrorUtils:"""平方误差最小化准则&#xff0c;选择其中最优的一个作为切分点对特征属性进行分箱处理"""staticmethoddef _set_sample_weight(sample_weight, n_samp…

Blender教程(基础)--试图的显示模式-22

一、透视模式&#xff08;AltZ&#xff09; 透视模式下可以实现选中透视的物体信息 发现选中了透视区的所有顶点 二、试图着色模式-显示网格边框 三、试图着色模式-显示实体 三、试图着色模式-材质预览 四、试图着色模式-显示渲染预览

深入解析MySQL 8:事务数据字典的变革

随着数据库技术的不断发展和完善&#xff0c;元数据的管理成为了一个日益重要的议题。在MySQL 8中&#xff0c;一项引人注目的新特性是引入了事务数据字典&#xff08;Transaction Data Dictionary&#xff0c;简称TDD&#xff09;&#xff0c;它改变了元数据的管理方式&#x…

医学图像隐私保护

随着数字医疗技术的快速发展&#xff0c;医学图像例如X光片、CT扫描、MRI及超声波扫描已成为现代医疗診断和治療的基石。然而&#xff0c;同时这些包含敏感个人信息的图像也面临着隐私和安全方面的挑战。随着数据泄露事件的增多&#xff0c;医学图像隐私保护变得尤为重要。 从…

Ps:直接从图层生成文件(图像资源)

通过Ps菜单&#xff1a;文件/导出/将图层导出到文件 Layers to Files命令&#xff0c;我们可以快速地将当前文档中的每个图层导出为同一类型、相同大小和选项的独立文件。 Photoshop 还提供了一个功能&#xff0c;可以基于文档中的图层或图层组的名称&#xff0c;自动生成指定大…

CleanMyMacX4.14.6如何清理mac垃圾内存

一直以来&#xff0c;苹果电脑的运行流畅度都很好&#xff0c;但是垃圾内存多了磁盘空间慢慢变少&#xff0c;还是会造成卡顿的。这篇文章就告诉大家电脑如何清理垃圾内存&#xff0c;电脑如何清理磁盘空间。 一、电脑如何清理垃圾内存 垃圾内存指的是各种缓存文件和系统垃圾…

Java图形化界面编程——事件处理 笔记

2.6 事件处理 前面介绍了如何放置各种组件&#xff0c;从而得到了丰富多彩的图形界面&#xff0c;但这些界面还不能响应用户的任何操作。比如单击前面所有窗口右上角的“X”按钮&#xff0c;但窗口依然不会关闭。因为在 AWT 编程中 &#xff0c;所有用户的操作&#xff0c;都必…

多个总体均值的比较(多元方差分析)

多元方差分析是什么 多元方差分析是一种统计方法&#xff0c;用于比较两个或更多组的均值在一个或多个自变量上的差异是否具有统计学意义。它可以同时考虑多个自变量对因变量的影响&#xff0c;以及自变量之间的交互作用。它是广义线性模型的拓展&#xff0c;适用于因变量为连…

JMeter使用教程

作为一名开发工程师&#xff0c;当我们接到需求的时候&#xff0c;一般就是分析需要&#xff0c;确定思路&#xff0c;编码&#xff0c;自测&#xff0c;然后就可以让测试人员去测试了。在自测这一步&#xff0c;作为开发人员&#xff0c;很多时候就是测一下业务流程是否正确&a…

Python 小白的 Leetcode Daily Challenge 刷题计划 - 20240209(除夕)

368. Largest Divisible Subset 难度&#xff1a;Medium 动态规划 方案还原 Yesterdays Daily Challenge can be reduced to the problem of shortest path in an unweighted graph while todays daily challenge can be reduced to the problem of longest path in an unwe…

用Python来实现2024年春晚刘谦魔术

简介 这是新春的第一篇&#xff0c;今天早上睡到了自然醒&#xff0c;打开手机刷视频就被刘谦的魔术所吸引&#xff0c;忍不住用编程去模拟一下这个过程。 首先&#xff0c;声明的一点&#xff0c;大年初一不学习&#xff0c;所以这其中涉及的数学原理约瑟夫环大家可以找找其…

【新书推荐】7.3 for语句

本节必须掌握的知识点&#xff1a; 示例二十四 代码分析 汇编解析 for循环嵌套语句 示例二十五 7.3.1 示例二十四 ■for语句语法形式&#xff1a; for(表达式1;表达式2;表达式3) { 语句块; } ●语法解析&#xff1a; 第一步&#xff1a;执行表达式1&#xff0c;表达式1…

LabVIEW工业监控系统

LabVIEW工业监控系统 介绍了一个基于LabVIEW软件开发的工业监控系统。系统通过虚拟测控技术和先进的数据处理能力&#xff0c;实现对工业过程的高效监控&#xff0c;提升系统的自动化和智能化水平&#xff0c;从而满足现代工业对高效率、高稳定性和低成本的需求。 随着工业自…

BestEdrOfTheMarket:一个针对AVEDR绕过的训练学习环境

关于BestEdrOfTheMarket BestEdrOfTheMarket是一个针对AV/EDR绕过的训练学习环境&#xff0c;广大研究人员和信息安全爱好者可以使用该项目研究和学习跟AV和EDR绕过相关的技术知识。 支持绕过的防御技术 1、多层API钩子&#xff1b; 2、SSH钩子&#xff1b; 3、IAT钩子&#x…

springboot176基于Spring Boot的装饰工程管理系统

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

嵌入式开发——linux系统怎么知道接了多少物理内存?

1、前言 linux系统是不知道当前设备接了多少内存&#xff0c;需要bootloader在启动时告诉linux系统感知到当前设备接了多少物理内存有两种方式 动态识别&#xff08;X86架构大多是这种&#xff09;&#xff1a;可以插拔的内存条&#xff0c;bootloader能识别出内存条的容量代码…

【Make编译控制 01】程序编译与执行

目录 一、编译原理概述 二、编译过程分析 三、编译动静态库 四、执行过程分析 一、编译原理概述 make&#xff1a; 一个GCC工具程序&#xff0c;它会读 makefile 脚本来确定程序中的哪个部分需要编译和连接&#xff0c;然后发布必要的命令。它读出的脚本&#xff08;叫做 …

react中hook封装一个table组件 与 useColumns组件

目录 1&#xff1a;react中hook封装一个table组件依赖CommonTable / index.tsx使用组件效果 2&#xff1a;useColumns组件useColumns.tsx使用 1&#xff1a;react中hook封装一个table组件 依赖 cnpm i react-resizable --save cnpm i ahooks cnpm i --save-dev types/react-r…