数据结构_第十三关(3):归并排序、计数排序

news2024/11/18 10:41:05

目录

归并排序

1.基本思想:

2.原理图:

1)分解合并

2)数组比较和归并方法:

3.代码实现(递归方式):

4.归并排序的非递归方式

原理:

情况1:

情况2:

情况3:

非递归代码实现

归并排序的特性总结:

计数排序

基本思想:

算法原理:

算法升级

计算排序的特点:

代码实现

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

什么时稳定性?

各种常见排序算法的总结


归并排序

1.基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,

该算法是采用分治法(Divide andConquer)的一个非常典型的应用。

将已有序的子序列合并,得到完全有序的序列;

即先使每个子序列有序,再使子序列段间有序。

若将两个有序表合并成一个有序表,称为二路归并。

2.原理图:

1)分解合并

第一层将一个数组分两个大组,

第二层再继续分,直到分成每组都只有一个为止

分解完了之后就是进行合并,每两个小数组,按顺序合并成一个大的数组

最终,合并称为一个有序的集合

动图效果:

归并排序是在原数组上进行的,用一个临时数组来做归并,把归并好的元素复制回原数组

2)数组比较和归并方法:

用上述长度为4的集合举例:

第一步:比较p1和p2位置元素的大小,谁的小,将谁的值放到p位置,并将指向小的元素的那个指针++,并且将p++

1比2小,放1到p位置,p1++,p++

 ......

第二步:按第一步的步骤,逐一比较,直到有一个指针走到了集合之外如下:

此时p2已经走到了集合外,就可以退出循环了

 第三步:放一个循环,将没走完的那个集合的剩余元素按顺序放到大集合种即可

当p走到大集合外面时结束循环

3.代码实现(递归方式):

//归并排序(递归实现)

//归并子函数
//(在遇到需要malloc扩容的函数时,将malloc代码放入主函数,另写一个子函数用来完成递归)
void _MergeSort(int* a, int begin ,int end, int* temp)
{
	//最后只剩下一个数的时候就说明begin=end,返回
	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);    //递归右半区

	//归并
	int begin1 = begin, end1 = mid;     //左区间[begin1, end1]
	int begin2 = mid + 1, end2 = end;   //右区间[begin2, end2]

	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");
		exit(-1);
	}
	_MergeSort(a, 0, n-1, temp);

	free(temp);
	temp = NULL;
}

4.归并排序的非递归方式

原理:

控制每次参与归并的元素即可,可以先定义一个变量rangeN,让其来划分区域

 开始时rangeN=1,区域为1则是有序,

i = i + 2*rangeN     定义 i 来区分每块区域

左区域:[begin1,end1]                右区域:[begin2,end2]

上图情况是一个特殊情况,如果遇到不能被完全划分左右区域对称的情况分为以下三种:

情况1:

当最后一个区域进行归并时,最后一组的左区间越界,所以需要对左区间的end1进行控制

情况2:

当最后一个区域进行归并时,最后一组的右区间全部越界,所以需要对右区间的begin2进行控制

情况3:

 当最后一个小组进行归并时,由于右区间越界,所以我们需要对右区间end2进行控制

非递归代码实现

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	// 归并每组数据个数,从1开始,因为1个认为是有序的,可以直接归并
	int rangeN = 1;

	while (rangeN < n) 
	{
		for (int i = 0; i < n; i += 2 * rangeN)
		{
			// [begin1,end1][begin2,end2] 归并

			int begin1 = i, end1 = i + rangeN - 1;
			int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;

			int j = i;

			// end1 begin2 end2 越界的三种情况
            //一定需要按顺序进行判断,不然会出错
			
            //end1越界,情况1,
            //解决:直接退出本次循环,可以不让后面的进行归并,再下一次循环时再排序
            if (end1 >= n)
			{
				break;
			}

            //begin2出界,情况2,
            //解决:直接退出本次循环,可以不让后面的进行归并,再下一次循环时再排序
			else if (begin2 >= n)
			{
				break;
			}

            //end2越界,情况3
            //解决:让end2等于数组最后的下标
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

            //开始按顺序归并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			// 归并一部分,拷贝一部分
			memcpy(a + i, tmp + i, sizeof(int)*(end2 - i + 1));
		}

		rangeN *= 2;
	}

	free(tmp);
	tmp = NULL;
}

归并排序的特性总结:

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

计数排序

基本思想:

之前学习的排序,无论是希尔排序,还是堆排序,都用的是比较两个数大小的方式进行的排序

而计数排序不是用比较的方法来进行排序的,它利用的是数组下标的计数来进行的排序

具体实现方法如下:

算法原理:

现假设有一组0~4的数据需要排序:{ 2,3,3,4,0,3,2,4,3 }

创建一个临时数组temp来依次记录每个数的出现次数

原数组中,

        0出现了1次,就在temp下标为0的位置记录1

        1没有出现,  就在temp下标为1的位置记录0

        2出现了2次,就在temp下标为0的位置记录2

        3出现了4次,就在temp下标为0的位置记录3

        4出现了2次,就在temp下标为0的位置记录2

数组每一个下标位置的值,代表了数列中对应整数出现的次数。

有了这个 “统计结果”,排序就很简单了。
直接遍历数组输出数组元素的下标值元素的值是几,就输出几次:

这样就能得到一个有序序列了

这就是计数排序的过程,因为他没有进行数与数之间的比较,
所以他的性能甚至快过那些O( log N) 的排序

可以看到上面的排序是一种特殊情况,那如果我们要排序的数组是  9000~9009,那么是不是得浪费前九千多个空间?

又或者是在原数组里面有负数的情况下是不是九不能进行计数排序了?

这就要对现有的算法进行一些小小的升级了

算法升级

例如我们有一组这样的数:9004,9001,9002,9005,9004,9001

这个数组,最大是9005,但最小的数是9001,如果用长度为9005的数组,那么按照上面的方法排序,肯定会太过浪费

事实上我们只需要开辟大小为5的数组即可(最大数 - 最小数 + 1)9005 - 9001 +1 = 5

用下标为0的记录9001,用下标为4的记录9005

当然,对于负数也一样可以使用,这里就不一一展示了

计算排序的特点:

  1. 稳定性:稳定
  2. 时间复杂度:O(n+k)
  3. 空间复杂度:O(n+k)

对于数据率不是很大,并且数据比较集中时,计数排序是一个很有效的排序算法

计数排序的局限性:

不能对小数进行排序,只适用于整数

代码实现

分4步实现

  1. 找到数组中的最大最小值
  2. 计算范围,开辟临时数组空间
  3. 统计相同元素出现次数
  4. 根据计数结果将序列依次放回原来的数组中
//计数排序
void CountSort(int* a, int n)
{
	//确定数组里面的最大最小值
	int max = a[0], min = a[0];
	for (int i = 1;i < n;i++)
	{
		if (a[i] < min)
			min = a[i];
		if (a[i] > max)
			max = a[i];
	}

	//范围 = 最大值 - 最小值 + 1
	//比如从 0 到 9 有10个数
	int range = max - min + 1;
	//用calloc开辟range个大小为int的空间,并给每个元素赋值为0
	int* countA = (int*)calloc(range, sizeof(int));
	if (countA == NULL)
	{
		perror("calloc fail");
		exit(-1);
	}

	//统计相同元素出现的次数
	for (int i = 0;i < n;i++)
	{
		//a[i] - min 是a[i]下标在countA里面对应的相对位置
		countA[a[i] - min]++;
	}
	
	//排序,根据计数结果将序列依次放回原来的数组中
	int k = 0;
	for (int j = 0;j < range;j++)
	{
		while (countA[j]--)
		{
			//j + min 就是数组元素的大小
			a[k++] = j + min;
		}
	}

	free(countA);
}

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

什么时稳定性?

稳定性的价值:

比如再考试排名的时候,第三名种有三个人的成绩相同,那么如果先交卷的人是第三名的话,就要去再成绩排序的时候保证其稳定性

各种常见排序算法的总结

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

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

相关文章

docker的三种镜像创建

目录 dock的三种镜像创建 基于现有的镜像创建 基于本地模板创建 基于Dockerfile 创建 联合文件系统 镜像加载原理 为什么Docker里的centos的大小才200M&#xff1f; Docker 镜像结构的分层 Dockerfile 操作常用的指令 Dockerfile格式 dockerfile构建apache实例 dock的…

探索【Stable-Diffusion WEBUI】各种插件和追求更高效

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;界面与翻译(1.1) 主题风格&#xff08;kitchen Theme&#xff09;(1.2) 对照翻译&#xff08;Bilingual Localization&#xff09;(1.3) 自行翻译(1.3) 提示词翻译&#xff08;Prompt Translator&#xff09…

asp.net765数码手机配件租赁系统

员工部分功能 1.员工登录&#xff0c;员工通过自己的账号和密码登录到系统中来&#xff0c;对租赁信息进行管理 2.配件查询&#xff0c;员工可以查询系统内的配件信息 3.客户信息管理&#xff0c;员工可以管理和店内有业务往来的客户信息 4.配件租赁&#xff0c;员工可以操作用…

字节蝉联全球独角兽榜首,ChatGPT企业狂升200多位

江山代有才人出&#xff0c;各领风骚数百年。 4月18日&#xff0c; 胡润研究院发布了《2023全球独角兽榜》&#xff0c;列出了全球成立于2000年之后&#xff0c;价值10亿美元以上的非上市公司。 榜单显示&#xff0c;全球一共有1361家独角兽企业入围榜单&#xff0c;分布在48个…

不要再问为什么在职读研?在中国人民大学与加拿大女王大学金融硕士项目听到了最好的答案

为什么要在职读研呢&#xff1f;平时工作已经很忙很累了&#xff0c;再利用业余时间去学习是为什么呢&#xff1f;有不满足于现状的&#xff0c;有一直为了自己目标努力冲刺的&#xff0c;有为了圆自己名校梦想的&#xff0c;也有单纯喜欢专业喜欢学校的。相信每个读研人都有自…

在时代的浪潮中实在前行,实在智能应邀出席浪潮数字企业2023生态伙伴大会

4月15日&#xff0c;以“新产品 新路径 新生态&#xff0c;共赢2023”为主题的浪潮数字企业生态伙伴大会在济南召开。 浪潮集团执行总裁王兴山、浪潮数字企业总经理魏代森、浪潮数字企业副总经理兼CTO郑伟波、IDC中国副总裁兼首席分析师武连峰等业内专家、实在智能等行业领军企…

三步搞定centos虚拟机的克隆

vm17centos7min版本&#xff0c;三步实现大数据的基础操作 一 在vm中选择克隆选项二 配置IP地址三 三台设备基于秘钥实现免密登录1 三台设备分别执行下面的指令&#xff0c;产生公钥和私钥2 给hadoop124和hadoop123 和hadoop122执行下列指令&#xff0c;将123和124的公钥拷贝给…

【小程序云开发】30分钟搭建个人相册小程序

文章目录 前言准备工作小程序架构创建小程序云开发环境创建数据库搭建个人相册写在最后 前言 图片存储&#xff0c;是所有应用开发里最常见的场景之一。 本文将通过实战“个人相册小程序”开发&#xff0c;教你如何借助小程序 云开发 能力&#xff0c;提升功能开发效率&#x…

【环境篇 1】CC2340环境搭建

文章目录 1 准备安装条件2.安装工具2.1 CCS12.1 安装2.2 下载并安装CC23XX SDK2.3 下载并安装对应开发环境和版本的 Sysconfig2.4 下载对应版本Free-RTOS2.5 下载并安装 TI Clang 3 工程编译3.1 环境修改3.2 导入工程2.4 编译项目 1 准备安装条件 CCS IDE编译工具&#xff0c;…

React 路由react-router-dom详解

React 路由react-router-dom详解 ( 路由嵌套 路由传参 路由权限 路由优化 按需导入 404页面 ) 前面我们先了解一下 路由是什么&#xff1f; 路由分类有哪些&#xff1f;内置API有哪些&#xff1f; 可能有点枯燥&#xff0c;不喜欢看的直接跳过&#xff01; 1&#xff0c;相…

2023年软考中级网络工程师考试大纲

1.考试目标 通过本考试的合格人员能根据应用部门的要求进行网络系统的规划、设计和网络设备的软硬件安装调试工作&#xff0c;能进行网络系统的运行、维护和管理&#xff0c;能高效、可靠、安全地管理网络资源&#xff0c;作为网络专业人员对系统开发进行技术支持和指导&#…

几十个简要的游戏案例分析

文章目录 一、 介绍二、 影响游戏体验的因素三、 游戏能爆火的因素1.影响游戏爆火因素的排名2.玩游戏的两种经典心理3.经典案例分析Qq农场植物大战僵尸水果忍者召唤神龙羊了个羊 4.游戏公司可借鉴的经验 四、 几十款游戏的多方面分析FC红白游戏机十二人街霸热血高校系列魂斗罗系…

vsftpd 3.0.3升级到3.0.5后的坑

1、问题描述 vsftpd 3.0.3升级到3.0.5后&#xff0c;Java ftps连接不成功&#xff0c;报以下错误&#xff1a; javax.net.ssl.SSLHandshakeException: Remote host terminated the handshakeat java.base/sun.security.ssl.SSLSocketImpl.handleEOF(SSLSocketImpl.java:1715…

Java日志处理

日志 日志就是Logging&#xff0c;它的目的是为了取代System.out.println() 输出日志&#xff0c;而不是用System.out.println()&#xff0c;有以下几个好处&#xff1a; &#xff08;1&#xff09;可以设置输出样式&#xff0c;避免自己每次都写“ERROR: ” var &#xff0…

故障分析 | 一次规律的 MySQL 主从延迟跳变

作者&#xff1a;李彬 爱可生 DBA 团队成员&#xff0c;负责项目日常问题处理及公司平台问题排查。爱好有亿点点多&#xff0c;吉他、旅行、打游戏… 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注…

WxGL应用实例:绘制高精度的3D太阳系模型

文章目录 1 坐标系的选择1.1 黄道坐标系1.2 三维空间直角坐标系 2 使用JPL星历表计算轨道2.1 日期时间2.2 特定时刻天体的位置2.3 天体运行轨道 3 太阳系模型3. 1 全家福3.2 时间、距离和半径的缩放3.3 黄道坐标系模型 天何所沓&#xff1f;十二焉分&#xff1f;日月安属&#…

拆解Open ODS View和HANA Composite Provider

这两个也不是新面孔了。 那么OODS和HCPR到底他俩怎么用&#xff1f;既然大家都是虚拟的&#xff0c;不占地方。那这俩infoprovider到底有啥区别&#xff1f; 首先就是目的不同。 HCPR是可以用Union和Join。也就是老的Multiprovider和InfoSet。Union就是说两个数据集的行能被…

UniLM模型简单介绍

目录 一、概要 二、深入扩展 2.1 预训练任务 2.2 模型精调 一、概要 如果将基于Transformer的双向语言模型&#xff08;如BERT模型中的掩码语言模型&#xff09;与单向的自回归语言模型&#xff08;如BART模型的解码器&#xff09;进行对比&#xff0c;可以发现&#xff0c…

常见的注册中心Nacos、Eureka

常见的注册中心 1.Eureka&#xff08;原生&#xff0c;2.0遇到瓶颈&#xff0c;停止维护&#xff09; 2.Zookeeper&#xff08;支持&#xff0c;专业的独立产品。例如&#xff1a;dubbo&#xff09; 3.Consul&#xff08;原生&#xff0c;GO语言开发&#xff09; 4.Nacos …

中国社科院与美国杜兰大学金融管理硕士项目——在职读研的日子里藏着我们未来无限可能

人生充满期待&#xff0c;梦想连接着未来。每一天都可以看作新的一页&#xff0c;要努力去成为最好的自己。在职读研的光阴里藏着无限的可能&#xff0c;只有不断的努力&#xff0c;不断的强大自己&#xff0c;未来会因为你的不懈坚持而发生改变&#xff0c;纵使眼前看不到希望…