排序算法第四辑——归并排序与计数排序

news2024/11/28 0:43:18

 

目录

一,归并排序算法

二,归并排序的非递归版本

三,计数排序


 

一,归并排序算法

归并排序算法是一个特别经典的算法了。这个算法采用的思想就是一个分治的思想,也就是将大问题化为子问题的思想。这个思想其实我们经常见到了,递归用的就是这个思想。那在归并排序上我们该如何用这个思想来解决问题呢?先举一个例子:

比如我们要排序下列数组:

 现在我们要将这个数组内的数据排成升序该怎么搞呢?关键步骤有三步:

1.将数据的最左边与最右边的下标找到记作:begin  end。

2.找到数组的中间下标mid将数组分成[begin,mid],[mid+1,end]两组数据。

3.先将左右区间排好序最后再合并成一个数组完成排序。

总结起来就是:分解,排序,归并

以数组[5,1,10,3,0,7]为例,排序的过程就是这样的:

将这个过程写成代码便实现了归并排序算法。

代码如下:

代码:

/归并排序递归版本
void MergeSort_(int* a, int begin, int end,int* tmp)
{
	//递归结束条件
	if (begin == end)
	{
		return;
	}
	//分解
	int midi = (begin+end) / 2;

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

	MergeSort_(a, begin1,end1,tmp);
	MergeSort_(a, begin2, end2,tmp);

	//合并
	int i = begin;
	while (begin1<=end1&&begin2<=end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//将未排完的拼接到尾巴上
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a+begin, tmp+begin, (end - begin + 1)*sizeof(int));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	MergeSort_(a, 0, n - 1, tmp);
	free(tmp);
}

 注意的点:

1.数组中的数据个数是最右边的下标-最左边的下标+1.

 (end - begin + 1)*sizeof(int)

2.合并数组的时候因为是先从左边开始合并的,在左边合并了以后。右边的合并就要从a+begin开始。临时的数组tmp也要从tmp+begin开始。这个begin可以看成已拷贝的元素个数。 

二,归并排序的非递归版本

 归并排序的算法有递归版本就一定会有非递归版本。归并排序的非递归版本的思想还是和递归版本的思想一样,都是先分割然后再将其归并排序拷贝回原数组中。拷贝的方式也有两种:

1.边归并边拷贝

2.归并完了以后再整体拷贝回原数组中。

这两种归并方法由于其拷贝的方式不同,所以在处理边界的方式上也会有所不同。现在来看看这两种不同拷贝方法的归并排序代码:

代码1:边归并边拷贝

void MergeSortNonR(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)
		{   //利用i来控制两个要归并排序的数组的边界
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = i + gap, end2 = begin2 + gap - 1;
            //修正
			if (end1 >= n||begin1>=n )
			{
				break;
			}
			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++];
				}
			}
            //处理尾巴,这个必须先处理[begin1,end1]
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <=end2)
			{
				tmp[j++] = a[begin2++];
			}
            //归并一组拷贝一组,注意两个数组拷贝位置的变化
			memcpy(a+i, tmp+i, (end2 - i+1) * sizeof(int));
		}
        //归并下一组
		gap= 2*gap;
	}
}

过程演示:

比如我要排序数组[5,1,4,6,9,4,6]这组数据按照的归并排序过程大概就是这样的:

在归并排序中最容易混淆的就是边界问题

在定义begin1,end1,begin2,end2时我们是这样定义的:

int begin1 = i, end1 = begin1 + gap - 1;
int begin2 = i + gap, end2 = begin2 + gap - 1;

其中i的范围是0~n-1:

for (int i = 0;i < n;i+=2*gap)

所以begin是不可能越界的,越界的只可能是end1,begin2,end2。越界的情况就分三种:

1.end1,begin2,end2全部越界。

2.begin2,end2越界。

3.end2越界。

越界的情况如下图所示:

因为归并排序需要首先将数据放到不同的区间排序,所以每次分成的区间个数是2的倍数,

但是当end1与begin2越界时有效的区间的个数是奇数,当end2越界时有第二个区间有一部分数据有效。所以在这里就这样子处理:

if (end1 >= n||begin1>=n )//当有效区间数为奇数时就不需要归并最后一个区间
			{
				break;
			}
			if (end2 >= n)//当有效区间数是偶数但是只有一部分区间数据有效时只归并有效的区间
			{
				end2 = n - 1;
			}

 这样就可以解决区间越界的问题了。

代码2:整组拷贝

void MergeSortNonR2(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 = begin1 + gap - 1;
			int begin2 = i + gap, end2 = begin2 + gap - 1;
			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if(begin2>=n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			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, tmp, n * sizeof(int));
		gap = 2 * gap;
		
	}
	free(tmp);
}

 

整组拷贝的代码与边归并边排序的代码大同小异,但是有几个关键点不同。

1.整组拷贝是没有不拷贝到tmp中的情况的。所以就不能再break。

所以这里的越界修正和边归并边拷贝不一样。整组拷贝的修正条件是这样的:

if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if(begin2>=n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if(end2>=n)
			{
				end2 = n - 1;
			}

不存在的区间就改成不存在的情况。

三,计数排序

 计数排序是一种非比较排序。这个算法运用到的思想是映射思想。为了理解起来更加的方便,现在我们先把计数排序算法实现。代码如下:

代码:

//计数排序
void CountSort(int* a, int n)
{   //计算出数组中的最大最小值
	int min = a[0];
	int max = a[0];
	for (int i = 0;i < n;i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
		if (a[i] > max)
		{
			max = a[i];
		}
	}
    //计算出最大和最小值之间的范围并创建数组count
	int range = max - min+1;
	int* count = (int*)malloc(sizeof(int)*range);
	if (count == NULL)
	{
		perror("malloc fail\n");
		return;
	}
    //将数组count里的数据都设置为0
	memset(count, 0, range * sizeof(int));
    //用count记录a[i]中每一个数据出现的次数,count的下标是a[i]的映射
	for (int i = 0;i < n;i++)
	{
		count[a[i] - min]++;
	}
    //根据每个数据出现的次数依次放回到数组a中,记住要加上min
	int k = 0;
	for (int i = 0;i < range;i++)
	{
		while (count[i]--)
		{
			a[k++] = i + min;
		}
	}
    //释放掉count
	free(count);
}

 图解原理:

 比如我要用计数排序来排数组[9,3,4,6,1,7,0,5,1,2]

 首先求出最大最小值:min = 0,max= 9.所以count数组的空间就开10个。

映射关系如下图所示:

 

然后根据count数组内的每一个数据都对应着下标+min这个数出现的次数。按照下标依次放回到原数组中,便可以得到排序好的原数组。

#注意#:这个排序只能排序整型数据,因为数组下标必须是整型值。 

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

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

相关文章

如何用双指针法解决力扣“反转单词前缀”问题

本篇博客会讲解力扣“2000. 反转单词前缀”的解题思路&#xff0c;这是题目链接。 本题的思路是&#xff1a;先调用strchr函数&#xff0c;在字符串word中查找字符ch&#xff0c;若找到了&#xff0c;则会返回一个非空指针p&#xff0c;指向ch在word中的位置。为了反转从word到…

下一个更大元素 I 力扣 HashMap + Deque栈 JAVA

nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。 给你两个 没有重复元素 的数组 nums1 和 nums2 &#xff0c;下标从 0 开始计数&#xff0c;其中nums1 是 nums2 的子集。 对于每个 0 < i < nums1.length &#xff0c;找出…

2023年北京/上海/深圳CSPM-3中级国标项目管理认证报名

CSPM-3中级项目管理专业人员评价&#xff0c;是中国标准化协会&#xff08;全国项目管理标准化技术委员会秘书处&#xff09;&#xff0c;面向社会开展项目管理专业人员能力的等级证书。旨在构建多层次从业人员培养培训体系&#xff0c;建立健全人才职业能力评价和激励机制的要…

基于ESP32-S3-BOX-Lite的语音合成与播报系统(esp-idf+WiFi+HTTPS+TTS)

目录 项目介绍硬件介绍项目设计开发环境及工程目录硬件初始化WiFiHTTPS请求TTS语音合成与播报cJSON解析TTS初始化语音合成与播报 附加功能按键回调LVGL数据可视化显示 功能展示项目总结 &#x1f449; 【Funpack2-3】基于ESP32-S3-BOX-LITE的B站粉丝数语音播报系统 &#x1f44…

ThinkPHP 远程一对多关联

用远程一对多关联的前提 如果模型 A 想远程一对多关联模型 C&#xff0c;前提是中间模型 B 对应的数据库表必须有模型 A 对应的数据表的外键&#xff0c;模型 C 对应的数据库表必须有模型 B 对应数据库表的外键。&#xff08;套娃&#xff09; 举例&#xff0c;商品获取商品评…

《世纪桥》期刊简介及投稿邮箱

《世纪桥》期刊简介及投稿邮箱 一、《世纪桥》期刊简介&#xff1a; 《世纪桥》以服务党史研究、总结执政经验、关注改革实践、透析时代热点、展现党员风采、传播先进文化、繁荣学术事业为宗旨&#xff0c;以发展和培养学术新人为已任&#xff0c;倡导学术的当代性、应用性和…

C# WPF编辑时显示图片,运行时不显示图片的解决方案

1、WPF语法 <Image Source"/ObjectName;component/Images/graph.png"/>2、设置图片属性 复制到输出目录设为&#xff1a;始终复制 生成操作设置为&#xff1a;资源

Spring-AOP(面向切面)

Spring-AOP(面向切面) 场景模拟(计算器) 功能接口 public interface Calculator {int add(int i, int j);int minus(int i, int j);int multiply(int i, int j);int div(int i, int j); }实现类 public class CalculateLogImpl implements Calculator {Overridepublic int …

PerfView 洞察那些 C# 代码中的短命线程

一&#xff1a;背景 1. 讲故事 这篇文章源自于分析一些疑难dump的思考而产生的灵感&#xff0c;在dump分析中经常要寻找的一个答案就是如何找到死亡线程的生前都做了一些什么&#xff1f;参考如下输出&#xff1a; 0:001> !t ThreadCount: 22 UnstartedThread: 0 Ba…

浏览器打开新的页面时自动打开控制台

需求 打开浏览器新tab时自动打开控制台&#xff0c;捕捉初次的网络请求 解决 在浏览器图标属性中加入以下代码&#xff0c;再次打开浏览器 --auto-open-devtools-for-tabs

Django实现接口自动化平台(十四)测试用例模块Testcases序列化器及视图【持续更新中】

相关文章&#xff1a; Django实现接口自动化平台&#xff08;十三&#xff09;接口模块Interfaces序列化器及视图【持续更新中】_做测试的喵酱的博客-CSDN博客 本章是项目的一个分解&#xff0c;查看本章内容时&#xff0c;要结合整体项目代码来看&#xff1a; python django…

1000+设计施工模型免费下载,助力设计方案制作和汇报场景搭建!

作为一名工程设计、施工人员&#xff0c;设计方案制作、工程汇报场景搭建的情景再常见不过。日常需要的模型是必不可少的&#xff0c;但最令人头大的问题是如何寻找方案素材。想要表达的信息越多&#xff0c;素材获取就越是苦恼&#xff01; 有没有一款软件能够集方案三维汇报…

边缘计算:连接物理与数字世界的智能桥梁

引言&#xff1a; 边缘计算&#xff08;Edge Computing&#xff09;作为一种分布式计算模型&#xff0c;旨在将数据处理和分析推向网络边缘设备。随着物联网和大数据的快速发展&#xff0c;边缘计算成为了解决数据处理延迟、网络带宽压力和隐私安全等问题的重要技术。本文将深入…

OLED拼接屏采购指南:如何选择最佳方案?

OLED拼接屏作为一种创新的大屏幕显示设备&#xff0c;正在成为各行各业信息展示和传播的重要工具。 然而&#xff0c;面对市场上众多的品牌和型号&#xff0c;如何选择最佳的OLED拼接屏方案成为一项关键任务。 本文将为您提供一份全面且实用的OLED拼接屏采购指南&#xff0c;…

pdf怎么转换为word格式?这5个实用的方法分享!

在现代数字化时代&#xff0c;PDF&#xff08;Portable Document Format&#xff09;文件已经成为广泛使用的文件格式之一。由于其固定的格式和可移植性&#xff0c;PDF文件在共享和传输文档时非常方便。然而&#xff0c;当我们需要编辑或修改PDF文件时&#xff0c;PDF的固定格…

菜品管理模块开发 -- 手把手教你做ssm+springboot入门后端项目黑马程序员瑞吉外卖(五)

文章目录 前言一、文件上传下载1. 文件上传介绍2. 文件下载介绍3. 实现文件上传功能4. 实现文件下载功能5. 上传下载图片效果预览 二、新增菜品1. 需求分析2. 数据模型3. 代码开发4. 功能测试 三、菜品信息分页查询1. 需求分析2. 代码开发3. 功能测试 四、修改菜品1. 需求分析2…

3.JAVA BIO深入剖析

highlight: arduino-light 1.JAVA BIO深入剖析 1.1 Java BIO 基本介绍 Java BIO 就是传统的 java io 编程&#xff0c;其相关的类和接口在 java.ioBIO(blocking I/O) &#xff1a; 同步阻塞&#xff0c;服务器实现模式为一个连接一个线程&#xff0c;即客户端有连接请求时服务器…

东莞-戴尔R540服务器故障告警处理方法

DELL PowerEdge R540服务器故障维修案例&#xff1a;&#xff08;看到文章就是缘分&#xff09; 客户名称&#xff1a;东莞市某街道管理中心 故障机型&#xff1a;DELL R540服务器 故障问题&#xff1a;DELL R540服务器无法开机&#xff0c;前面板亮黄灯&#xff0c;工程师通过…

PatchMatchNet运行dtu数据集、

文章目录 1 准备工作2 dtu数据集重建演示2.1 bash文件超参数解释2.2 lists/dtu解释1 准备工作 MVSNet、PatchMatchNet环境配置 数据集下载,准备好数据集,我的dtu数据集放在/PatchmatchNet-main/Data/testData/dtu/下,如图所示 进入mvs 环境:source activate mvs 2 dtu数据…

Appium 安卓环境的配置

目录 前言&#xff1a; 环境准备 写个脚本玩玩 前言&#xff1a; 在使用Appium进行安卓自动化测试之前&#xff0c;需要配置相应的安卓环境。 环境准备 为了避免走弯路&#xff0c;我们先要确保三点&#xff1a; Android SDK API > 17 (Additional features require …