数据结构-归并排序+计数排序

news2024/9/21 15:40:52

1.归并排序

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

相当于每次把待排数据分为两个子区间,如果每个子区间有序,再让两个子区间归并起来也有序,那整体就有序了。我们可以按照二叉树的思想,把子区间再分为两份,使子区间的子区间有序.......直到子区间分无可分为止。

具体过程如下:

那该如何让两个有序子区间归并呢?

直接在数组中肯定不行,这样会发生数据的覆盖。所以我们可以像之前合并两个有序数组一样,另外开辟一个空间tmp,依次比较两个有序子区间的值,每次比较后把较小的放在tmp中,如果其中一个子区间提前结束,就把另外一个子区间的剩余的数据全放进tmp,最后把tmp中的数据拷贝回原数组。

使用递归实现:

#include<stdio.h>
#include<stdlib.h>
void _MegeSort(int* a, int begin, int end,int*tmp)
{
	//只剩一个数据,递归结束
	if (begin == end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	//递归子区间,分为两部分
	_MegeSort(a, begin, mid, tmp);
	_MegeSort(a, mid+1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int j = begin;

	//两部分比较,每次小的放入tmp
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[j++] = a[begin1++];
		}
		else
		{
			tmp[j++] = a[begin2++];
		}
	}
	//哪部分有剩余,全部放入tmp
	while (begin1 <= end1)
	{
		tmp[j++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[j++] = a[begin2++];
	}
	//拷贝到原数组
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MegeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MegeSort(a, 0, n - 1, tmp);
	free(tmp);
}

void Print(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ",a[i]);
	}
	printf("\n");
}
int main()
{
	int a[] = { 1,4,9,6,3,5,2,8,10,7,11,1};
	MegeSort(a, sizeof(a) / sizeof(int));
	Print(a, sizeof(a) / sizeof(int));
	return 0;
}

注意:

1. 因为每次递归的子区间都不一定是从0开始的,所以我们拷贝数据时,最好从begin的位置开始:

//拷贝到原数组
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));

2. 在代码中j作为tmp的坐标,每次往tmp中放入数据后都要加一,但不能初始化为0,否则每次递归进入,j的值都会清0,所以最好初始化:j=begin

归并排序的复杂度:

时间复杂度O(N*logN)

归并排序每次递归都要把待排数据分为两份,相当于二分法,那一共有logN层递归,而每次递归都要比较数据,要把每个数据都遍历一遍,每层的时间复杂度就是O(N),所以总共的时间复杂度是O(N*logN)。

空间复杂度:O(N) 

刚开始就开辟了空间,此时就已经是O(N)了,而递归过程中函数栈帧的创建是logN,所以总的空间复杂度是:O(N+logN),但是量级没变,还是O(N)。

2.非递归实现归并排序

非递归实现归并排序,我们只需模拟上述的递归过程即可,把递归过程转换为:把数据先分为2个一组,全部归并一遍,拷贝回原数组,然后4个一组,全部归并一遍,拷贝回原数组,再8个一组, 全部归并一遍,拷贝回原数组,

那我们就可以设置一个gap,两个数据为一组时,gap=1,每归并一组数据就往后跳2*gap步,直到全部归并一遍,再次分组,这次gap=2,每归并一组数据往后跳2*gap步,直到全部归并一遍,下次gap=4,跳2*gap步.....,直到gap>n就停止,

代码如下:

void MegeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail\n");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		int j = 0;
		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;

			//两部分比较,每次小的放入tmp
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			//哪部分有剩余,全部放入tmp
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
		}
		memcpy(a, tmp, sizeof(int) * n);
		gap *= 2;
	}
	free(tmp);
}

测试一下:

上面结果看起来,我们排序成功了,但是上述代码真的对吗?

上面代码我们在测试时用的是8个数据,但是如果用9个、10个等,就会发现排序并不会成功,可能程序还会崩掉,这是为什么呢?

因为我们在分组时,是按照固定的2的次方分的,一旦数据个数不是2、4、8的次方,后面归并时就会发生越界问题。

下面我们给10个数据打印一下边界,会发现,有三种越界的方式,:

那我们对这三种情况分别做一下处理:

第1、2种情况出现时,我们直接break,第三种情况,我们修改边界,令end2=n-1,但是注意直接break后,第1、2种情况往tmp中归并时会少一部分数据(如上图蓝框所示),所以最后把tmp的数据往a中拷贝时,不能一次性全部拷贝回去,否则a中这些数据就永远丢失了,所以最好归并一段,拷贝一段,这样拷贝过去的数据只会把前面的数据覆盖,没参与归并的数据还在a中。

代码如下:

void MegeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail\n");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		int j = 0;
		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;

			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			//修正
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			//两部分比较,每次小的放入tmp
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			//哪部分有剩余,全部放入tmp
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			//归并一段,拷贝一段
			memcpy(a+i, tmp+i, sizeof(int) * (end2-i+1));
		}
		gap *= 2;
	}
	free(tmp);
}

3.计数排序

基本思想:

1. 统计每个数据出现的次数。

2. 根据数据的次数排序。 

如果我们要排序的数在0~9之间,我们可以像上面一样开辟10个int大小的空间,统计待排数据中每个数据的个数,在开辟出的数组的相应下标处计数,那如果我们要排序的数据在100~109之间呢?难道开辟110个空间吗?

当然不是,我们可以做相对映射,在开辟空间之前,先找到待排数据中的最小值和最大值,开辟空间的大小就是:sizeof(int)*(max-min+1),开辟出的数组下标应该是:0~9,0~9下标的位置分别对应的是100~109,计数时,在下标为该数据减待排数据中的最小值的位置统计次数,例如:109就在109-100=9的下标处统计次数,统计完排序的时候再加上最小值即可。

代码如下:

#include<stdio.h>
#include<stdlib.h>
void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	//找最大值和最小值
	for (int i = 0; i < n; i++)
	{
		if (a[i] < a[0])
		{
			min = a[i];
		}
		if (a[i] > a[0])
		{
			max = a[i];
		}
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count, 0, sizeof(int) * range);
	//计数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	//排序
	int k = 0;
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[k++] = j + min;
		}
	}
}
//打印函数
Print(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
int main()
{
	int a[] = { 6,1,6,7,9,6,4,5,6,1 };
	CountSort(a, sizeof(a) / sizeof(int));
	Print(a, sizeof(a) / sizeof(int));
	return 0;
}

计数排序的复杂度:

时间复杂度:O(N+range)

寻找最大值和最小值时,遍历一遍数组,时间复杂度是:O(N),由于待排数据的范围是range,排序时所耗费的时间复杂度是:O(range),所以最终的时间复杂度是:O(N+range)

如果知道N和range的大小,N大,就是O(N),range大,就是O(range)

空间复杂度:O(range)

额外开辟的空间个数是range,所以空间复杂度就是:O(range)

4.排序的复杂度和稳定性

总结如下:

 以上就是排序学习的全部内容了,到这,数据结构的学习就告一段落了,近期会停更一段时间,用来复习,后面将继续学习C++的知识,

未完待续。。。

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

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

相关文章

机器人规划算法——movebase导航框架源码分析

这里对MoveBase类的类成员进行了声明&#xff0c;以下为比较重要的几个类成员函数。 构造函数 MoveBase::MoveBase | 初始化Action 控制主体 MoveBase::executeCb收到目标&#xff0c;触发全局规划线程&#xff0c;循环执行局部规划 全局规划线程 void MoveBase::planThread |…

大模型推理加速框架vllm部署的实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

CVE-2023-22515:Atlassian Confluence权限提升漏洞复现 [附POC]

文章目录 Atlassian Confluence权限提升(CVE-2023-22515)漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 Atlassian Confluence权限提升(CVE-2023-22515)漏洞复现 [附POC] 0x01 前言 免责声明&…

C练习题_3

一、单项选择题&#xff08;本大题共20小题&#xff0c;每小题2分&#xff0c;共40分。在每小题给出的四个备选项中,选出一个正确的答案&#xff0c;并将所选项前的字母填写在答题纸的相应位置上。 以下正确的C语言自定义标识符是() A. la B. 2a C. do D. a.12 2.在C语言中,错…

【libGDX】Mesh纹理贴图

1 前言 纹理贴图的本质是将图片的纹理坐标与模型的顶点坐标建立一一映射关系。纹理坐标的 x、y 轴正方向分别朝右和朝下&#xff0c;如下。 2 纹理贴图 本节将使用 Mesh、ShaderProgram、Shader 实现纹理贴图&#xff0c;OpenGL ES 的实现见博客 → 纹理贴图。 DesktopLauncher…

Matplotlib图形注释_Python数据分析与可视化

Matplotlib图形注释 添加注释文字、坐标变换 有的时候单单使用图形无法完整清晰的表达我们的信息&#xff0c;我们还需要进行文字进行注释&#xff0c;所以matplotlib提供了文字、箭头等注释可以突出图形中重点信息。 添加注释 为了使我们的可视化图形让人更加容易理解&#…

软件测试职业规划导图

公司开发的产品专业性较强&#xff0c;软件测试人员需要有很强的专业知识&#xff0c;现在软件测试人员发展出现了一种测试管理者不愿意看到的景象&#xff1a; 1、开发技术较强的软件测试人员转向了软件开发(非测试工具开发)&#xff1b; 2、业务能力较强的测试人员转向了软件…

C++初级项目webserver项目流程介绍(2)

一、引言 C的webserver项目是自己在学完网络编程后根据网课的内容做的一个初级的网络编程项目。 这个项目的效果是可以在浏览器通过输入网络IP地址和端口&#xff0c;然后打开对应的文件目录 效果如下&#xff1a; 也可以打开文件夹后点击目录&#xff0c;打开到对应的文件夹…

北京劲松HPV诊疗中心专业分析:扁平疣有什么特征?

扁平疣是一种常见的皮肤疾病&#xff0c;具有传染性&#xff0c;其主要特征包括皮肤出现扁平的丘疹、轻微瘙痒、好发于青少年等。今日特邀北京劲松HPV诊疗中心主任谭巍将详细介绍扁平疣的特征&#xff0c;希望借此能提高大众认知水平&#xff0c;以更好预防。 年轻漂亮的小芳是…

一体化污水处理设备各种材质的优缺点

一体化污水处理设备的材质有多种&#xff0c;包括不锈钢、玻璃钢、聚乙烯塑料、碳钢等。每种材质都有其独特的优点和缺点。 不锈钢材质的优点是防腐性能好&#xff0c;耐磨损&#xff0c;使用寿命长&#xff0c;且外观美观。其缺点是成本较高&#xff0c;不适合在一些特殊的环…

Django 通过 Trunc(kind) 和 Extract(lookup_name) 参数进行潜在 SQL 注入 (CVE-2022-34265)

漏洞描述 Django 于 2022 年6月4 日发布了一个安全更新&#xff0c;修复了 Trunc&#xff08;&#xff09; 和 Extract&#xff08;&#xff09; 数据库函数中的 SQL 注入漏洞。 参考链接&#xff1a; Django security releases issued: 4.0.6 and 3.2.14 | Weblog | Djang…

洛谷P1157组合的输出 递归:我他又来辣

没没没没没没没错&#xff0c;这是一道简单的递归&#xff08;其实是深搜加回溯) 我不管&#xff0c;我说是递归就是递归。 上题干&#xff1a; 题目描述 排列与组合是常用的数学方法&#xff0c;其中组合就是从 n 个元素中抽出 r个元素&#xff08;不分顺序且 r≤n&#x…

【阿里云服务器】2023安装宝塔面板8.0.4

文章目录 前言安装宝塔远程链接服务器输入安装宝塔命令放行宝塔端口 一键安装环境附录重装系统Linux系统卸载宝塔方式一方式二 遇见的问题 前言 镜像是CentOS 7.9.4 安装宝塔 远程链接服务器 输入安装宝塔命令 yum install -y wget && wget -O install.sh https://…

BGP基础配置

EBGP是AS之间 IBGP是AS内 R1-R2是EBGP,R4-R5是EBGP R2-R3-R4是IBGP 第一步基础配置&#xff1a;IP地址 [r1-GigabitEthernet0/0/0]ip ad 12.0.0.1 24 [r1-LoopBack0]ip ad 1.1.1.1 32 [r2-GigabitEthernet0/0/0]ip ad 12.0.0.2 24 [r2-LoopBack0]ip ad 2.2.2.2 32 [r2-Loop…

Educational Codeforces Round 158 (Rated for Div. 2)(A~E)(贪心,树形DP)

A - Line Trip 题意&#xff1a;有一条路&#xff0c;可以用一条数线来表示。你位于数线上的点 0 &#xff0c;你想从点 0 到点 x &#xff0c;再回到点 0。你乘汽车旅行&#xff0c;每行驶 1个单位的距离要花费 1 升汽油。当您从点 0出发时&#xff0c;汽车已加满油(油箱中的…

记录ruoyi-plus-vue部署的问题

ruoyi-vue-plus5.x 后端 ruoyi-vue-plus5.x 前端 前端本地启动命令 # 克隆项目 git clone https://gitee.com/JavaLionLi/plus-ui.git# 安装依赖 npm install --registryhttps://registry.npmmirror.com# 启动服务 npm run dev# 构建生产环境 yarn build:prod # 前端访问地址…

在Spring Boot中实现单文件,多文件上传

这篇文章算是一篇水文&#xff0c;因为也没啥好讲的&#xff0c;在Spring Boot中&#xff0c;上传文件是我们常常做的&#xff0c;包括我们在实际开发过程中&#xff0c;我们也经常碰到与文件上传有关的功能&#xff0c;这也算是我们常用的一个功能了&#xff0c;毕竟作为开发者…

5种主流API网关技术选型,yyds!

API网关是微服务项目的重要组成部分&#xff0c;今天来聊聊API网关的技术选型&#xff0c;有理论&#xff0c;有实战。 不 BB&#xff0c;上文章目录&#xff1a; 1 API网关基础 1.1 什么是API网关 API网关是一个服务器&#xff0c;是系统的唯一入口。 从面向对象设计的角度…

大众博客系统测试报告【改】

一、项目背景 大众博客系统采用前后端分离的方法来实现&#xff0c;同时使用了数据库来存储相关的数据&#xff0c;同时将其部署到云服务器上。前端主要有四个页面构成&#xff1a;登录页、列表页、详情页以及编辑页&#xff0c;以上模拟实现了最简单的大众博客系统。其结合后端…

Spine深入学习 —— 数据

atlas数据的处理 作用 图集&#xff0c;描述了spine使用的图片信息。 结构 page 页块 页块包含了页图像名称, 以及加载和渲染图像的相关信息。 page1.pngsize: 640, 480format: RGBA8888filter: Linear, Linearrepeat: nonepma: truename: 首行为该页中的图像名称. 图片位…