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

在这里插入图片描述

📷 江池俊: 个人主页
🔥个人专栏: ✅数据结构冒险记 ✅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,一经查实,立即删除!

相关文章

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

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

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

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

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;都必…

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;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

【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…

开源微服务平台框架的特点是什么?

借助什么平台的力量&#xff0c;可以让企业实现高效率的流程化办公&#xff1f;低代码技术平台是近些年来较为流行的平台产品&#xff0c;可以帮助很多行业进入流程化办公新时代&#xff0c;做好数据管理工作&#xff0c;从而提升企业市场竞争力。流辰信息专业研发低代码技术平…

软件文档测试

1 文档测试的范围 软件产品由可运行的程序、数据和文档组成。文档是软件的一个重要组成部分。 在软件的整人生命周期中&#xff0c;会用到许多文档&#xff0c;在各个阶段中以文档作为前阶段工作成果的体现和后阶段工作的依据。 软件文档的分类结构图如下图所示&#xff1a; …

图灵之旅--二叉树堆排序

目录 树型结构概念树的表示形式 二叉树概念特殊的二叉树二叉树性质二叉树的存储二叉树的遍历前中后序遍历 优先级队列(堆)概念 优先级队列的模拟实现堆的性质概念堆的存储方式堆的创建 堆常用接口介绍PriorityQueue的特性PriorityQueue常用接口介绍优先级队列的构造插入/删除/获…

力扣刷题之旅:进阶篇(六)—— 图论与最短路径问题

力扣&#xff08;LeetCode&#xff09;是一个在线编程平台&#xff0c;主要用于帮助程序员提升算法和数据结构方面的能力。以下是一些力扣上的入门题目&#xff0c;以及它们的解题代码。 --点击进入刷题地址 引言 在算法的广阔天地中&#xff0c;图论是一个非常重要的领域。…

2万字曝光:华尔街疯狂抢购比特币背后

作者/来源&#xff1a;Mark Goodwin and whitney Webb BitcoinMagazine 编译&#xff1a;秦晋 全文&#xff1a;19000余字 在最近比特币ETF获得批准之后&#xff0c;贝莱德的拉里-芬克透露&#xff0c;很快所有东西都将被「ETF化」与代币化&#xff0c;不仅威胁到现有的资产和商…

【linux系统体验】-archlinux折腾日记

archlinux 一、系统安装二、系统配置及美化2.1 中文输入法2.2 安装virtualbox增强工具2.3 终端美化2.4 桌面面板美化 三、问题总结3.1 一、系统安装 安装步骤人们已经总结了很多很全: Arch Linux图文安装教程 大体步骤&#xff1a; 磁盘分区安装 Linux内核配置系统&#xff…