排序算法之——归并排序,计数排序

文章目录

  • 前言
  • 一、归并排序
    • 1. 归并排序的思想
    • 2. 归并排序时间复杂度及空间复杂度
    • 3. 归并排序代码实现
      • 1)递归版本
      • 2)非递归版本
  • 二、计数排序
    • 1. 计数排序的思想
    • 2. 计数排序的时间复杂度及空间复杂度
    • 3. 计数排序代码实现
  • 总结(排序算法稳定性)


前言

今天我们一起来了解归并排序(MergeSort),以及一种非比较的计数排序(CountSort)~

在这里插入图片描述


一、归并排序

1. 归并排序的思想

归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的高效排序算法。它的核心步骤包括将一个数组分割成更小的子数组,对这些子数组进行排序,然后将它们合并成一个有序的数组。以下是归并排序的核心步骤详解:

归并排序的核心步骤:

  1. 分割(Divide)

    • 将待排序数组从中间分割成两个子数组(通常是左半部分和右半部分)。
    • 继续递归地将这两个子数组再分割,直到每个子数组的大小为 1(即只有一个元素),因为一个元素的数组是有序的。
  2. 归并(Merge)

    • 将两个有序的子数组合并成一个有序的数组。
    • 使用两个指针分别指向两个子数组的开头,比较指针指向的元素,将较小的元素放入新数组中,并移动相应的指针。
    • 当其中一个子数组的元素被全部放入新数组后,将另一个子数组剩余的元素直接复制到新数组的后面。
  3. 组合(Combine)

    • 在归并的过程中,逐步形成更大的有序数组,最终得到一个完整的有序数组。

总结:
归并排序是一种高效的排序算法,利用分治法的思想,通过将大问题分解为小问题来解决。其稳定性、时间复杂度 O(n log n) 以及适用于大规模数据的特性使其在实际应用中非常流行。

我们一起来看下面的图:
在这里插入图片描述

通过这张图我们可以很直观的感受到归并排序的分解和合并过程,它的步骤是这样的:

归并排序步骤总结 :

  1. 定义归并排序主函数

    • MergeSort 函数接受待排序数组和数组的大小作为参数。
    • 在函数中分配一个临时数组 tmp,用于辅助合并数组。其实我们合并出来的数据是在tmp中的,最后拷贝回去。
    • 调用 _MergeSort 函数进行排序。
    void MergeSort(int* arr, int n) {int* tmp = (int*)malloc(sizeof(int) * n); // 创建临时数组_MergeSort(arr, 0, n - 1, tmp); // 调用归并排序free(tmp); // 释放临时数组
    }
    
  2. 定义归并排序递归函数

    • _MergeSort 函数接受数组、左边界、右边界和临时数组作为参数。
    • 基本情况:如果 left 大于或等于 right,则返回,表示该子数组已经有序。
    void _MergeSort(int* arr, int left, int right, int* tmp) {if (left >= right) {return; // 基本情况}...
    }
    
  3. 分割数组

    • 计算中间索引 mid,将数组分割为左右两个部分。
    • 递归调用 _MergeSort 对左半部分(leftmid)和右半部分(mid + 1right)进行排序。

    在这里插入图片描述
    对于它的每一个左右两侧都要继续分割(以左侧为例):
    在这里插入图片描述

    int mid = (left + right) / 2;
    _MergeSort(arr, left, mid, tmp); // 递归排序左半部分
    _MergeSort(arr, mid + 1, right, tmp); // 递归排序右半部分
    
  4. 合并有序子数组

    • 定义两个指针 begin1begin2,分别指向左子数组和右子数组的起始位置。
    • 使用 index 指针在临时数组 tmp 中进行合并。

    在这里插入图片描述

    int begin1 = left, end1 = mid; // 左子数组范围
    int begin2 = mid + 1, end2 = right; // 右子数组范围
    int index = begin1; // 临时数组下标while (begin1 <= end1 && begin2 <= end2) {if (arr[begin1] < arr[begin2]) {tmp[index++] = arr[begin1++]; // 复制较小的元素} else {tmp[index++] = arr[begin2++];}
    }
    
  5. 处理剩余元素

    • 如果左子数组还有剩余元素,将其复制到 tmp 中。
    • 如果右子数组还有剩余元素,也将其复制到 tmp 中。
    while (begin1 <= end1) {tmp[index++] = arr[begin1++]; // 复制左子数组剩余元素
    }
    while (begin2 <= end2) {tmp[index++] = arr[begin2++]; // 复制右子数组剩余元素
    }
    
  6. 将合并结果复制回原数组

    • 将临时数组 tmp 中的有序元素复制回原数组 arr 的相应位置。
    for (int i = left; i <= right; i++) {arr[i] = tmp[i]; // 将临时数组的内容复制回原数组
    }
    

2. 归并排序时间复杂度及空间复杂度

归并排序(Merge Sort)是一种高效的排序算法,其时间复杂度和空间复杂度如下:

时间复杂度 :

  • 归并排序的时间复杂度为 O(n log n)
    • 分割阶段:每次将数组分成两个子数组,深度为 log n,表示分割的层数。

    • 合并阶段:在每一层中,合并两个子数组的过程需要 O(n) 的时间,因为每个元素都要被处理一次。

    • 因此,整个算法的时间复杂度可以表示为:
      在这里插入图片描述

    • 根据主定理,得到归并排序的时间复杂度为 O(n log n)。

空间复杂度 :

  • 归并排序的空间复杂度为 O(n)
    • 归并排序需要使用额外的临时数组来存储合并过程中的结果。在每次合并操作中,都会分配一个大小为 n 的临时数组,因此其空间复杂度为 O(n)。

总结 :

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(n)

3. 归并排序代码实现

1)递归版本

//归并排序
void _MergeSort(int* arr, int left, int right, int* tmp)
{if (left >= right){return;}int mid = (left + right) / 2;_MergeSort(arr, left, mid, tmp);_MergeSort(arr, mid + 1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;//index 为tmp下标,不一定是0,而是左边界int index = begin1;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}for (int i = left; i <= right; i++){arr[i] = tmp[i];}
}void MergeSort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);_MergeSort(arr, 0, n - 1, tmp);free(tmp);
}

2)非递归版本


/*
非递归排序与递归排序相反,将一个元素与相邻元素构成有序数组,
再与旁边数组构成有序数组,直至整个数组有序。
要有mid指针传入,因为不足一组数据时,重新计算mid划分会有问题
需要指定mid的位置
*/
void merge(int* a, int left, int mid, int right, int* tmp)
{// [left, mid]// [mid+1, right]int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[index++] = a[begin1++];elsetmp[index++] = a[begin2++];}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}/*
k用来表示每次k个元素归并
*/
void mergePass(int* arr, int k, int n, int* temp)
{int i = 0;//从前往后,将2个长度为k的子序列合并为1个while (i < n - 2 * k + 1){merge(arr, i, i + k - 1, i + 2 * k - 1, temp);i += 2 * k;}//合并区间[i, n - 1]有序的左半部分[i, i + k - 1]以及不及一个步长的右半部分[i + k, n - 1]if (i < n - k){merge(arr, i, i + k - 1, n - 1, temp);}}

二、计数排序

1. 计数排序的思想

计数排序是一种非比较排序算法,适用于范围有限的整数数据。它的核心思想是利用数组的索引来直接计算元素的位置,达到排序的目的。
1)统计相同元素出现次数
2)根据统计的结果将序列回收到原来的序列

我们来看下面这张图:
在这里插入图片描述
这是我们待排序的数组。

  • 第一步:定义一个新数组,使得旧数组的元素值是新数组的下标

假设,我们定义成这样行不行?
在这里插入图片描述
如果完全按照旧数组的元素值来开空间会造成大量的空间浪费,因此不可取。

这里还有一个问题,如果旧数组的元素值是负数怎么办呢?

这里空间问题可以和负数问题一起解决:

首先找到旧数组的最小值,以及最大值
在这里插入图片描述

遍历一遍数组很容易找到数组最小值为100,最大值为109.

那我们开的空间 range 就是最值差
在这里插入图片描述

  • 第二步:旧数组中每一个元素减去最小值min作为新数组下标,出现了几个新数组对应的下标中的元素就是多少,这样也顺便解决了负数问题,因为负数 - 更小的负数 = 正数。

在这里插入图片描述

  • 第三步:遍历新数组,只要数组数据不为零就循环次数(数组值),输出内容 (i + min),将所有元素拷贝回原来的数组,就完成了排序。
    在这里插入图片描述

2. 计数排序的时间复杂度及空间复杂度

计数排序是一种有效的排序算法,特别适用于数据范围有限的情况。根据你提供的代码,以下是计数排序的时间复杂度和空间复杂度分析:

时间复杂度:

计数排序的时间复杂度为 O(n + k),其中:

  • n:待排序数组的大小。
  • k:待排序元素的范围(最大值 - 最小值 + 1)。

分析过程

  1. 找最大最小值:遍历数组一次,找到最大值和最小值,时间复杂度为 O(n)。
  2. 创建计数数组:分配大小为 k 的计数数组,时间复杂度为 O(k)。
  3. 统计元素出现次数:再次遍历原数组,将每个元素的出现次数记录在计数数组中,时间复杂度为 O(n)。
  4. 放回排序结果:遍历计数数组,根据记录的次数将元素放回原数组,最坏情况下需要遍历 k 次,时间复杂度为 O(k)。

因此,总的时间复杂度为:
在这里插入图片描述

空间复杂度 :

计数排序的空间复杂度为 O(k),其中 k 是待排序元素的范围。

分析过程

  • 需要一个计数数组 count,其大小为 k,用于存储每个元素出现的次数。
  • 如果输出数组与原数组相同,空间复杂度会增加到 O(n + k)(包含原数组和计数数组),但通常我们只考虑额外空间,因此空间复杂度通常记为 O(k)。

总结

  • 时间复杂度:O(n + k)
  • 空间复杂度:O(k)

这种复杂度特性使得计数排序在处理范围有限的整数数据时非常高效。适合用于大规模的正整数排序,尤其在数值分布相对均匀的情况下。
但缺点也很明显,数据过于分散太浪费空间,而且只能处理整数数据


3. 计数排序代码实现

// 计数排序
void CountSort(int* arr, int n)
{int max = arr[0], min = arr[0];//找最大最小值for (int i = 1; i < n; i++){if (arr[i] > max)max = arr[i];if (arr[i] < min)min = arr[i];}//定义新数组空间大小int range = max - min + 1;int* count = (int*)malloc(range * sizeof(int));if (count == NULL){perror("malloc fail!");exit(1);}//初始化range数组中所有的数据为0memset(count, 0, sizeof(int) * range);//原来的下标成为元素,原来的(元素 - min) 成为下标,出现几次新的元素就加几次for (int i = 0; i < n; i++){count[arr[i] - min]++;}//把排好序的元素放回原数组int index = 0;for (int i = 0; i < range; i++){while (count[i]--){arr[index++] = i + min;}}
}

总结(排序算法稳定性)

到了这里我们所有排序思想已经学完了,再来一起总结一下吧!

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

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的
相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,⽽在排序后的序列中,r[i]仍在r[j]之
前,则称这种排序算法是稳定的;否则称为不稳定的。

在这里插入图片描述

在这里插入图片描述

谢谢大家~

在这里插入图片描述

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

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

相关文章

荣耀问鼎!宏山激光斩获2024年度行业创新大奖

8月28日&#xff0c;由高科技行业门户OFweek维科网主办的“维科杯OFweek2024激光行业年度评选”于中国深圳成功举办。宏山激光凭借出类拔萃的技术创新实力与卓越品质&#xff0c;成功斩获“维科杯OFweek2024年度激光行业最佳智能装备/自动化产线技术创新奖”。 这一殊荣绝非偶然…

RabbitMQ的应用问题

一、幂等性保障 幂等性是数学和计算机科学中某些运算的性质, 它们可以被多次应⽤, ⽽不会改变初始应⽤的结果 数学上的幂等性&#xff1a; f(x)f(f(x)) |x| 数据库操作幂等性&#xff1a; 数据库的 select 操作. 不同时间两次查询的结果可能不同, 但是这个操作是符合幂等性…

profinet转Ethernet网关在工业现场如何应用

一、项目背景 在某工业自动化系统中&#xff0c;现有的设备采用Profinet通信协议&#xff0c;而新引入的一些智能设备只支持Ethernet通信。为了实现不同协议设备之间的互联互通&#xff0c;决定采用开疆智能Profinet转Ethernet网关来解决通信兼容性问题。 二、硬件准备 1.支持P…

《C++》解密--单链表

目录 一、概念与结构 二、实现单链表 三、链表的分类 四、单链表算法题 一、概念与结构 1、节点 结点的组成主要有&#xff1a;当前结点要保存的数据和保存下一个节点的地址&#xff08;指针变量&#xff09; 图中指针变量plist保存的是第一个结点的地址&#xff0c;我们称p…

极限电流型氧传感器的工作原理以及有哪些应用场景?

极限电流型氧传感器的工作原理&#xff1a; 极限电流型氧传感器的工作原理基于稳定ZrO2固体电解质的氧泵作用。在已稳定化ZrO2两侧被覆铂电极&#xff0c;阴极侧用有气体扩散孔的罩接合&#xff0c;形成阴极空腔。在一定的温度下&#xff0c;当ZrO2电极两侧加一定电压时&#…

【渗透实战系列】|App渗透 ,由sql注入、绕过人脸识别、成功登录APP

涉及知识点 1、APP抓包和逆向破解加密算法&#xff1b; 2、解密参数&#xff0c;寻找注入点&#xff1b; 3、Union注入构造万能密码&#xff1b; 4、利用忘记密码功能&#xff0c;burpsuite爆破用户名&#xff1b; 5、解密短信验证数据包&#xff0c;绕过验证码&#xff0c;成功…

Docker笔记-Docker磁盘空间清理

无用的容器指的是已经停止运行且处于非活跃状态的容器。无用的镜像包括没有被任何容器使用的镜像&#xff0c;或者是被标记为"<none>"的镜像&#xff0c;通常是构建过程中产生的无标签镜像。 通过执行 docker container ls -a 和 docker image ls -a 命令&…

2024年软考——系统规划与管理师30天冲刺学习指南!!!

距离2024下半年软考系统规划与管理师考试已经只剩一个多月了&#xff0c;还没有开始备考的小伙伴赶紧行动起来。为了帮助大家更好的冲刺学习&#xff0c;特此提供一份考前30天学习指南。本指南包括考情分析、学习规划、冲刺攻略三个部分&#xff0c;可以参考此指南进行最后的复…

墙绘艺术在线交易平台:SpringBoot技术详解

4 系统设计 墙绘产品展示交易平台的设计方案比如功能框架的设计&#xff0c;比如数据库的设计的好坏也就决定了该系统在开发层面是否高效&#xff0c;以及在系统维护层面是否容易维护和升级&#xff0c;因为在系统实现阶段是需要考虑用户的所有需求&#xff0c;要是在设计阶段没…

【视频目标分割-2024CVPR】Putting the Object Back into Video Object Segmentation

Cutie 系列文章目录1 摘要2 引言2.1背景和难点2.2 解决方案2.3 成果 3 相关方法3.1 基于记忆的VOS3.2对象级推理3.3 自动视频分割 4 工作方法4.1 overview4.2 对象变换器4.2.1 overview4.2.2 Foreground-Background Masked Attention4.2.3 Positional Embeddings 4.3 Object Me…

RabbitMQ的高级特性-死信队列

死信(dead message) 简单理解就是因为种种原因, ⽆法被消费的信息, 就是死信. 有死信, ⾃然就有死信队列. 当消息在⼀个队列中变成死信之后&#xff0c;它能被重新被发送到另⼀个交换器 中&#xff0c;这个交换器就是DLX( Dead Letter Exchange ), 绑定DLX的队列, 就称为死信队…

EasyX与少儿编程:轻松上手的编程启蒙工具

EasyX&#xff1a;开启少儿编程的图形化启蒙之路 随着科技发展&#xff0c;编程逐渐成为孩子们教育中重要的一部分。如何让孩子在编程启蒙阶段更容易接受并激发他们的兴趣&#xff0c;成为许多家长和老师关心的问题。相比起传统的编程语言&#xff0c;图形化编程工具显得更直观…

【ehr】人事招聘薪资绩效考勤一体化管理系统(源码)

一、项目介绍 一款全源码可二开&#xff0c;可基于云部署、私有部署的企业级数字化人力资源管理系统&#xff0c;涵盖了招聘、人事、考勤、绩效、社保、酬薪六大模块&#xff0c;解决了从人事招聘到酬薪计算的全周期人力资源管理&#xff0c;符合当下大中小型企业组织架构管理运…

2k1000LA loongnix 安装java

问题&#xff1a; 客户 需要在 loongnix 上 使用 java 的程序。 情况说明&#xff1a; 使用 apt get 是无法 安装java 的。 按照的资料就行。 首先是 下载 loongarch64 的 java 的压缩包。这个我已经下载下来了。 社区下载地址&#xff1a; http://www.loongnix.cn/zh/api/…

书生大模型实战训练营 第三期 入门岛

1.Linux 任务一 完成SSH连接与端口映射并运行hello_world.py vscode自带的端口设置功能很方便 2.Python 任务一 实现wordcount函数 任务二 vscode 单步调试

【递归】9. leetcode 104 二叉树的最大深度

1 题目描述 题目链接&#xff1a;二叉树的最大深度 2 解答思路 递归分为三步&#xff0c;接下来就按照这三步来思考问题 第一步&#xff1a;挖掘出相同的子问题 &#xff08;关系到具体函数头的设计&#xff09; 第二步&#xff1a;只关心具体子问题做了什么 &#xff0…

物联网智能设备:未来生活的变革者

文章目录 引言什么是物联网智能设备&#xff1f;技术架构应用场景挑战与解决方案未来发展趋势结论 引言 随着科技的迅猛发展&#xff0c;物联网&#xff08;IoT&#xff09;正在改变我们生活的方方面面。从智能家居到工业自动化&#xff0c;物联网智能设备正在逐步融入我们的日…

四款视频剪辑工具使用感受与推荐:

大家好&#xff01;今天咱们来聊聊视频剪辑工具。随着短视频的火热&#xff0c;越来越多的小伙伴开始涉足视频剪辑领域&#xff0c;那到底哪款工具更适合你呢&#xff1f;接下来&#xff0c;就让我为大家分享一下我使用过的几款视频剪辑工具的体验和感受吧&#xff01; 一、福昕…

[linux 驱动]input输入子系统详解与实战

目录 1 描述 2 结构体 2.1 input_class 2.2 input_dev 2.4 input_event 2.4 input_dev_type 3 input接口 3.1 input_allocate_device 3.2 input_free_device 3.3 input_register_device 3.4 input_unregister_device 3.5 input_event 3.6 input_sync 3.7 input_se…

如何做好接口测试?||关于京东平台商品API接口测试

探讨主题&#xff1a;如何做好接口测试&#xff1f; 一、接口测试简介 1、什么是接口测试&#xff1f; 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制…