【初阶数据结构篇】归并排序和计数排序(总结篇)

news2025/1/16 1:01:17

文章目录

  • 归并排序和计数排序
    • 前言
    • 代码位置
    • 归并排序
    • 计数排序
    • 排序性能比较
    • 排序算法复杂度及稳定性分析

归并排序和计数排序

前言

本篇以排升序为例


代码位置

gitee
前篇:【初阶数据结构篇】冒泡排序和快速排序
中篇:【初阶数据结构篇】插入、希尔、选择、堆排序介绍


归并排序

基本思想

归并排序算法思想: 归并排序(MERGE-SORT)是建⽴在归并操作上的⼀种有效的排序算法,该算法是采⽤分治法(Divide andConquer)的⼀个⾮常典型的应⽤。将已有序的⼦序列合并,得到完全有序的序列;即先使每个⼦序列有序,再使⼦序列段间有序。若将两个有序表合并成⼀个有序表,称为⼆路归并。归并排序核⼼步骤:

在这里插入图片描述

  • 归并排序,顾名思义,先递归再合并,具体步骤如下:

  • 第一步,拿到左右下标,一直二分到只有一个元素

    • 以上图6 10这个函数栈帧为例,left=0,right=1,此时找到mid为0,这里二分为[0,0]和[1,1]两个区间,直接return这里二分时要带上mid,在快速排序中每个函数栈帧我们把基准值排到了正确的位置,所以再进行递归时不用管这个位置的元素了,而这里我们是从下往上操作的,先细分到一个元素再依次向上两两合并
  • 第二步,开始合并

    • 说白了就是合并两个有序数组的问题

    • 这里展示一道在顺序表算法题里讲过的例题

    • 给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。

      请你 合并 nums2nums1 中,使合并后的数组同样按 非递减顺序 排列。

      **注意:**最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n

      • 思路其实是一样的,只不过上面这个确实是两个数组
      • 但在归并排序里面,说是两个序列,但其实只是同一个数组里不同的区间,如果还在原数组操作会把数组的元素覆盖掉,无法排序。
      • 所以我们新创建了一个和原来数组一样的数组tmp,用来存储我们排序后的顺序
    • 其他思路就是一样的了,依次比较,然后把小的放在前面,最后跳出循环时再把没有越界的指针后面元素依次放到tmp就行,这样就实现了有序序列的合并了注意在放入tmp时候也要保证下标对应

  • 第三步,我们要排序的是原数组的元素,所以每次在[left,right]区间内排好的序列要复制给arr

  • 经过不断的合并回归,最后在arr数组里得到的就是排好序的序列了

  • 代码如下,思路很简单,但就是第一次看整个代码可能有点懵

void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)
	{
		return;
	}
	int mid = (left + right) / 2;
	//[left,mid] [mid+1,right]
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);


	//合并
	//[left,mid] [mid+1,right]
	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++];
		}
	}
	//要么begin1越界但begin2没有越界  要么begin2越界但begin1没有越界
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//[left,mid] [mid+1,right]
	//把tmp中的数据拷贝回arr中
	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);
}
  • 复杂度分析
    • 时间复杂度:递归深度logn,对于每一层来说都是会把所有元素遍历一次,例如在递归第一层(二叉树第一层的函数栈桢)把原区间分为了两个区间,最后回归的时候就是合并两个有序数组,会把其中元素都遍历一次;其他层都同理为n,总计O(nlogn)。
    • 空间复杂度:递归深度logn,开辟n个元素的空间,为O(n)。

且这个复杂度很稳定,不会像快速排序一样因为基准值的好坏或着排序序列的变化而改变(二分序列一旦左右下标有了就定了,而在快排中左右序列区分还要基于基准值,基准值的位置一旦“一边倒”就会导致很差的时间和空间复杂度。)


计数排序

  • 前面讲的七中排序方法都是比较排序法,意味着我们在函数的实现中都或多或少通过比较了两个元素的大小来进行排序
  • 这里介绍一种非比较排序方法,计数排序

基本思想

计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应⽤。

至于什么是哈希直接定址法,其实就是一种映射,也是计数排序的主要思想。

操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中
  • 开辟数组空间大小max-min+1
  • 将数组所有值减去最小值
  • 所以开辟数组下标就是[0,max-min]
  • 相当于把数组内数据大小整体往前平移了min个单位当做数组的下标(min可以为负数)

原因如下:

在这里插入图片描述

  • 第一步,确定数组最大值和最小值,遍历
  • 第二步,申请空间并初始化
  • 第三步,统计每个数据出现的个数
  • 根据申请的数组下标递增依次往原数组放数据

这样我们就把本来乱序的数据通过数组下标依次递增这一特点进行了排序

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(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//初始化range数组中所有的数据为0
	memset(count, 0, range * sizeof(int));

	//统计数组中每个数据出现的次数
	for (int i = 0; i < n; i++)
	{
		count[arr[i] - min]++;
	}
	//取count中的数据,往arr中放
	int index = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			arr[index++] = i + min;
		}
	}
}

  • 复杂度分析
  • 时间复杂度:找最大和最小值为N,统计个数也是遍历原数组为N,最后往原数组放数据,相当于遍历一次新数组为range,加上把N个数据放到原数组,所以总时间复杂度O(N+range)
  • 空间复杂度:O(range)

特性分析

计数排序在数据范围集中时,效率很⾼,但是适⽤范围及场景有限。

比如只能用来排整数数据。


排序性能比较

  • 更加直观的体会到各种排序算法的时间复杂度差异
  • 使用随机数函数造数据,然后排序打印不同算法排序时间
// 测试排序的性能对⽐
void TestOP()
{
	srand(time(0));
	const int N = 100000; 

	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);
	int* a8 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
		a8[i] = a1[i];
	}

	int begin7 = clock();
	BubbleSort(a7, N);
	int end7 = clock();

	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();
	QuickSort(a5, 0, N - 1);
	//QuickSortNonR(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();
	MergeSort(a6, N);
	int end6 = clock();

	int begin8 = clock();
	CountSort(a8, N);
	int end8 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("BubbleSort:%d\n", end7 - begin7);
	printf("CountSort:%d\n", end8 - begin8);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
	free(a8);
}

在这里插入图片描述

可见希尔排序,堆排序,归并排序以及计数排序的优越性


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

基本概念

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

简单来说就是重复的数据在排序前后相对位置是否发生改变

稳定性验证案例

  • 直接选择排序:5 8 5 2 9
  • 希尔排序:5 8 2 5 9
  • 堆排序:2 2 2 2
  • 快速排序:5 3 3 4 3 8 9 10 11

代入排序方法一一验证即可发现这些排序是不稳定的

在这里插入图片描述

以上就是归并排序和计数排序方法的介绍啦,同时也对八大排序算法进行了比较总结,各位大佬有什么问题欢迎在评论区指正,您的支持是我创作的最大动力!❤️
请添加图片描述

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

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

相关文章

【Qt】QDateTimeEdit

在Qt中&#xff0c;QDateEdit是用于选择日期的微调框&#xff0c;QTimeEdit是用于选择小时和分钟的微调框 QDateTimeEdit则是基于QDateEdit和QTimeEdit的组合控件&#xff0c;能够同时显示日期和时间&#xff0c;并允许用户以交互方式编辑日期 常用属性 属性说明dateTime时间…

electron-updater实现electron全量更新和增量更新——渲染进程UI部分

同学们可以私信我加入学习群&#xff01; 正文开始 前言更新功能所有文章汇总一、两个同心球效果实现二、球内进度条、logo、粒子元素实现2.1 球内包含几个元素&#xff1a;2.2 随机粒子生成方法generateRandomPoint2.3 创建多个粒子的方法createParticle 三、gsap创建路径动画…

基于python的百度迁徙迁入、迁出数据分析(六)

书接上回&#xff0c;苏州市我选取了2024年5月1日——5月5日迁入、迁出城市前20名并求了均值&#xff0c;从数据中可以看出苏州市与上海市的关系还是很铁的&#xff0c;都互为对方的迁入、迁出的首选且迁徙比例也接近4分之一&#xff0c;名副其实的老铁了&#xff1b; 迁出城市…

Seurat-SCTransform与harmony整合学习续(亚群分析)

目录 提取细胞亚群 SCTransform-harmony技术路线 ①亚群SCTransform标准化 ②harmony去批次 这里对上一章的内容进行补充&#xff1a; Seurat-SCTransform与harmony整合学习-CSDN博客 提取细胞亚群 rm(list ls()) library(Seurat)#好像先后需要先后加载 library(patchw…

【Jenkins】在linux上通过Jenkins编译gitee项目

因项目需求近期在linux服务器上部署了Jenkins来自动编译gitee上的项目源码&#xff0c;期间踩到了一些坑&#xff0c;花费了不少时间来处理&#xff0c;特此记录。 所需资源下载列表&#xff1a; Jenkins &#xff1a;https://mirrors.tuna.tsinghua.edu.cn/jenkins/war/2.46…

文件系统 --- 重定向,缓冲区

序言 本篇文章的内容和上一篇文章 &#x1f449;点击查看 紧密相连&#xff0c;所以为了更好的理解本篇文章&#xff0c;需要大家将前置知识准备好哦&#x1f607;。  本文主要向大家介绍文件的重定向&#xff0c;以及基于用户级别的缓冲区和基于操作系统级别的缓冲区。原来看…

AI技术和大模型对人才市场的影响

012024 AI技术和大模型 2024年AI技术和大模型呈现出多元化和深入融合的趋势&#xff0c;以下是一些关键的技术方向和特点&#xff1a; 1. 生成式AI 生成式AI&#xff08;Generative AI&#xff09;在2024年继续快速发展&#xff0c;它能够创造全新的内容&#xff0c;而不仅仅…

Redis——有序集合

目录 1. 添加元素 ZADD 2. 查看全部元素 ZRANGE 3. 查看某个元素的分数 ZSCORE 4. 查看元素的排名 ZRANK SortedSet 也叫 ZSet ,即有序集合&#xff0c; 有序集合与集合的区别&#xff1a; 有序集合的每个元素都会关联一个浮点类型的分数&#xff0c;依赖该分数的的大小对…

《Milvus Cloud向量数据库指南》——多模态融合新纪元:音频、视频与文本的无缝转换

在探讨多模态数据处理与应用的广阔领域中,多模态文本、音频、视频数据的融合与交互成为了近年来人工智能研究的热点之一。这一趋势不仅推动了技术的深度发展,也为众多行业带来了前所未有的创新机遇。本文将深入剖析多模态文本-音频与多模态文本-视频RAG(Retrieval-Augmented…

书生大模型基础岛-第三关:LangGPT结构化提示词编写实践

1.来源和任务 来源&#xff1a; https://github.com/InternLM/Tutorial/blob/camp3/docs/L1/Prompt/task.md 任务&#xff1a; 背景问题&#xff1a;近期相关研究发现&#xff0c;LLM在对比浮点数字时表现不佳&#xff0c;经验证&#xff0c;internlm2-chat-1.8b (internlm2-…

C++——list容器以及手动实现

LIST容器 list概述列表容器属性例子 list函数构造函数默认构造函数&#xff1a;带有元素个数和元素初值的构造函数&#xff1a;范围构造函数&#xff1a;拷贝构造函数&#xff1a;移动构造函数&#xff1a;示例 赋值运算符重载拷贝赋值操作符 (1)&#xff1a;移动赋值操作符 (2…

安全通信|数据加密的由来|加密算法简介|中间人攻击与证书认证|身份验证

&#x1f448;️下一篇 计算机网络-专栏&#x1f448;️ 数据加密的由来|加密算法简介|中间人攻击与证书认证 引言 在客户端(client)-服务器(server)模式下&#xff0c;客户端与服务器间通信&#xff0c;如果明文传输数据&#xff0c;在传输过程被劫持&#xff0c;内容直接泄…

MySQL触发器和存储过程

1、触发器 &#xff08;1&#xff09;&#xff1a;建立触发器&#xff0c;订单表中增加订单数量后&#xff0c;商品表商品数量同步减少对应的商品订单出数量,并测试 mysql> create trigger orders_after_insert_trigger-> after insert on orders for each row-> up…

不知道你们有没有我这样的一种状态...总是这样又总是那样...

各位小伙伴们&#xff0c;我是风尚&#xff0c;我不知道你们有没有那么一刻&#xff0c;就是感觉站在人生的十字路口&#xff0c;感觉自己就像是被扔进了一个没有导航的迷宫&#xff0c;四周都是墙&#xff0c;头顶是蓝天白云&#xff0c;却怎么也找不到出口的方向&#xff1f;…

重磅推荐!GBD再度登顶Lancet!| GBD数据库周报(7.17~7.23)

全球疾病负担&#xff08;GBD&#xff09;是迄今为止规模最大、最全面的一项研究&#xff0c;旨在量化不同地区和不同时期的健康损失&#xff0c;从而改善卫生系统并消除差异。 该研究由华盛顿大学健康指标与评估研究所 (IHME) 牵头&#xff0c;是一项真正的全球性研究&#xf…

MySQL--数据库与表的操作

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 数据库的基本操作 # 1、查看数据库show databases;​# 2、创建数据库create database 数据库名称;​# 3、删除数据库drop databse 数据库名称; 数据表…

RAC(Teamcenter )开发,Bom行解包和打包的方法

1、打包 UnpackAllAction allAction new UnpackAllAction((AbstractBOMLineViewerApplication) currentApplication, "packAllAction"); new Thread(allAction).start();2、解包 UnpackCommand command new UnpackCommand(bomLine); command.executeModal();3、注…

Marin说PCB之Orcad Capture调网表时出现了“Duplicate Pin Name”该怎么搞?

最近大巴黎在如火如荼的举行着奥运会&#xff0c;中国健儿们也是不负众望在很多项目中取得金牌的好成绩&#xff0c;其中中国选手陈芋汐/全红婵夺得巴黎奥运会跳水女子双人10米台金牌&#xff0c;其实没有看我就知道比赛的结果了&#xff0c;肯定是我们中国队夺得金牌的。 看到…

月薪竟然高达60k,AI大模型凭什么?

你是不是最近经常看到或听到“AI大模型”这个关键词&#xff1f;我也是&#xff01;所以好奇去Boss直聘上搜了下工作机会。看到结果时&#xff0c;我有点不淡定了&#xff01;薪资竟然这么高&#xff01; 这是我随便搜的结果&#xff0c;发出来给大家看看。 下面&#xff0c;我…