算法——归并排序和计数排序

news2024/11/15 9:44:50

Ⅰ. 归并排序

1. 基本思想

归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法( Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

图示如下

  

2. 递归代码实现

//打印数组内容
void ArrPrint(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	//当右边的下标小于左边时,结束递归
	if (begin >= end)
	{
		return;
	}

	//取中间数mid,将当前内容二分为[begin, mid],[mid+1, end]
	int mid = (begin + end) / 2;
	//如果需要排序后半的话就必须重置一个新的begin来保证是从后半的位置开始(而非0处)
	int begin1 = begin;
	//如果需要排序前半的话就必须重置一个新的end来保证是在前半的位置结束(而非n-1处)
	int end1 = mid;
	//保证形式的统一
	int begin2 = mid + 1;
	int end2 = end;

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

	//在从最后的递归返回后,返回的是前后两个有序的数组
	//起点与终点分别是arr1:[begin1, end1] arr2:[begin2, end2]
	//单趟归并
	int begin3 = begin1;//记录前数组begin1的位置,作为tmp的起始位置
	//当遍历完两个数组中的其中一个时,结束循环
	while (begin1 <= end1 && begin2 <= end2)
	{
		//两数组中较小的数取出并存放在tmp对应位置上
		if (a[begin1] < a[begin2])
		{
			tmp[begin3++] = a[begin1++];
		}
		else
		{
			tmp[begin3++] = a[begin2++];
		}
	}

	//将剩下那个数组的值接在tmp上
	//两个数组都进行判断,因为有一个数组已经完成排序不会进入循环
	while (begin1 <= end1)
	{
		tmp[begin3++] = a[begin1++];
	}

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

	//将tmp数组值拷贝回a
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* a, int n)
{
	//创建一个大小与a相同的数组,归并返回数据
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	//传入数组左右下标进行排序
	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
	tmp = NULL;
}

我们用以下代码来验证

void test()
{
	int a[] = { 5,6,7,4,1,9,2,8,3,0 };
	int sz = sizeof(a) / sizeof(a[0]);
	MergeSort(a, sz);
	ArrPrint(a, sz);
}

int main()
{
	test();

	return 0;
}

3. 非递归形式

对于有2的n次方个数据,如下的一组数据归并有

 观察可以发现,第一次归并的间距为2(即2-0),第二次的间距为4(即4-0),由此我们可以得到如下代码

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

	// 间隙为1,也代表2个一组进行归并
	int gap = 1;
	// 间隙为n/2时,代表n个一组归并
	while (gap < n)
	{
		// 保证每次刚好归并排序对应2组的数据
		for (int i = 0; i < n; i += gap * 2)
		{
			// 确定前后两组的头与尾
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			// 初始化tmp中开始储存数据的位置为begin1
			int begin3 = begin1;

			// 归并数据
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[begin3++] = a[begin1++];
				}
				else
				{
					tmp[begin3++] = a[begin2++];
				}
			}

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

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

			// 从a+i的位置将有效数据从tmp全部拷贝回a
			memcpy(a + i, tmp + i, sizeof(int)*(begin3 - i));
		}

		gap *= 2;
	}
}

而在大多数时候,数据个数并不总是2的n次方个数,因此需要我们作出修改,一般来说有如下几种情况

因此,有两种方案

一、将每次边界在数组外的部分存放在tmp中,最后一次性拷贝回a中

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

	// 间隙为1,也代表2个一组进行归并
	int gap = 1;
	// 间隙为n/2时,代表n个一组归并
	while (gap < n)
	{
		// 保证每次刚好归并排序对应2组的数据
		for (int i = 0; i < n; i += gap * 2)
		{
			// 确定前后两组的头与尾
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			// 初始化tmp中开始储存数据的位置为begin1
			int begin3 = begin1;

			//第1种情况
			if (end1 >= n)
			{
				// 将[begin1,end1]这一部分调整为end1截止,后面部分为不存在的区间
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if(begin2 >= n)// 第2种
			{
				// 将[begin1,end1]这一部分调整为end1截止,后面部分为不存在的区间
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)// 第3种
			{
				// 将[begin2,end2]这一部分调整为end2截止
				end2 = n - 1;
			}

			// 手动打印边界查看范围
			printf("[%d,%d][%d,%d]", begin1, end1, begin2, end2);

			// 归并数据
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[begin3++] = a[begin1++];
				}
				else
				{
					tmp[begin3++] = a[begin2++];
				}
			}

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

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

		}
		// 将tmp的所有数据拷贝回a
		memcpy(a, tmp, sizeof(int) * n);

		printf("\n");
		gap *= 2;
	}
}

验证有

二、将每次边界在数组外的部分放弃存放在tmp中,每次拷贝tmp的一部分回a中

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

	// 间隙为1,也代表2个一组进行归并
	int gap = 1;
	// 间隙为n/2时,代表n个一组归并
	while (gap < n)
	{
		// 保证每次刚好归并排序对应2组的数据
		for (int i = 0; i < n; i += gap * 2)
		{
			// 确定前后两组的头与尾
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			// 初始化tmp中开始储存数据的位置为begin1
			int begin3 = begin1;

			// 第1,2种情况
			if (end1 >= n || begin2 >= n)
			{
				// 只要end1或begin2越界,就放弃本次[begin1,end1]的排序
				break;
			}
			else if(end2 >= n)// 第三种
			{
				// begin2未越界end2越界,将end2调整为最后一个元素进行排序
				end2 = n - 1;
			}

			// 手动打印边界查看范围
			printf("[%d,%d][%d,%d]", begin1, end1, begin2, end2);

			// 归并数据
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[begin3++] = a[begin1++];
				}
				else
				{
					tmp[begin3++] = a[begin2++];
				}
			}

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

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

			// 将tmp中对应数据数据拷贝回a
			memcpy(a + i, tmp + i, sizeof(int) * (begin3 - i));
		}

		printf("\n");
		gap *= 2;
	}
}

验证有

Ⅱ. 计数排序

1. 基本思想

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中

  

2. 代码实现

//打印数组内容
void ArrPrint(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void CountSort(int* a, int n)
{
	// 选出相对区间内的最大值与最小值
	int min, max;
	max = min = a[0];

	for (int i = 0; i < n; i++)
	{
		if (max < a[i])
		{
			max = a[i];
		}
		if (min > a[i])
		{
			min = a[i];
		}
	}

	// 创建一个大小数值在[min,max]内的临时数组
	int* count = (int*)calloc((max - min + 1), sizeof(int));
	if (count == NULL)
	{
		perror("calloc fail");
		return;
	}

	for (int i = 0; i < n; i++)
	{
		// a[i] ∈ [min,max]
		// a[i]-min ∈ [0,max-min]
		// 因此对应下标++就代表对应数值+1
		count[a[i] - min]++;
	}

	int j = 0;
	// 将数值不为0的依次取出复制到原数组中
	for (int i = 0; i < max - min + 1; i++)
	{
		// 对应计数数组为0时表明不含该元素
		while (count[i]-- != 0)
		{
			a[j++] = i + min;
		}
	}
}

在此,我们用下面的代码来验证

void test()
{
	int a[] = { 100,105,104,102,103,104,107,100,105,106 };
	int sz = sizeof(a) / sizeof(a[0]);
	CountSort(a, sz);
	ArrPrint(a, sz);
}

int main()
{
	test();

	return 0;
}

 有

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

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

相关文章

Python爬虫| 一文掌握XPath

本文概要 本篇文章主要介绍利用Python爬虫爬取付费文章&#xff0c;适合练习爬虫基础同学&#xff0c;文中描述和代码示例很详细&#xff0c;干货满满&#xff0c;感兴趣的小伙伴快来一起学习吧&#xff01; &#x1f31f;&#x1f31f;&#x1f31f;个人简介&#x1f31f;&…

公司来了个00后,我愿称之为卷王之王,卷的让人崩溃...

前几天我们公司一下子来了几个新人&#xff0c;看样子好像都是一些00后&#xff0c;这些年轻人是真能熬啊&#xff0c;本来我们几个老油子都是每天稍微加会班就打算下班走了&#xff0c;这几个新人一直不走&#xff0c;搞得我们也不好提前走。 2023年春招已经过去了&#xff0…

网络安全前景怎么样?怎么自学?看这一篇就够了

一、网络安全前景 网络安全行业细分岗位比较多&#xff0c;目前需求量比较大的几类网络安全岗位有渗透测试、安全运维、等保测评等&#xff0c;在岗位需求量和薪资待遇方面都比较可观。 这时很多人就会问&#xff0c;网络安全人才需求量这么大&#xff0c;进入行业的人就会越来…

【redis】案例--迷你版微信抢红包

系列文章目录 文章目录 系列文章目录前言在这里插入图片描述 一、业务描述二、需求分析三、架构设计关键点拆红包算法 二倍均值算法 图解 四、编码实现 RedPackageController整体思路&#xff1a;发红包代码进入拆分红包算法抢红包代码 五、多学一手 前言 一、业务描述 二、需求…

鲁棒优化入门(四)——超详细讲解:两阶段鲁棒优化以及列与约束生成算法(CCG)的matlab+yalmip代码实现

本文的主要参考文献&#xff1a; Zeng B , Zhao L . Solving Two-stage Robust Optimization Problems by A Constraint-and-Column Generation Method[J]. Operations Research Letters, 2013, 41(5):457-461. 1.两阶段鲁棒优化问题的引入 鲁棒优化是应对数据不确定性的一种优…

从零玩转设计模式之单例模式-danlimos

title: 从零玩转设计模式之单例模式 date: 2022-12-12 12:41:03.604 updated: 2022-12-23 15:35:29.0 url: https://www.yby6.com/archives/danlimos categories: - 单例模式 - 设计模式 tags: - Java模式 - 单例模式 - 设计模式 前言 单例设计模式是23种设计模式中最常用的设…

面试题背麻了,花3个月面过华为测开岗,拿个26K不过分吧?

计算机专业&#xff0c;代码能力一般&#xff0c;之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发&#xff0c;第二份实习由于大三暑假回国的时间比较短&#xff08;小于两个月&#xff09;&#xff0c;于是找的实习是在一家初创…

基于 ZYNQ 的电能质量系统高速数据采集系统设计

随着电网中非线性负荷用户的不断增加 &#xff0c; 电能质量问题日益严重 。 高精度数据采集系统能够为电能质 量分析提供准确的数据支持 &#xff0c; 是解决电能质量问题的关键依据 。 通过对比现有高速采集系统的设计方案 &#xff0c; 主 控电路多以 ARM 微控制器搭配…

抖音seo排名系统/账号矩阵源码关键词开发部署

抖音seo排名系统/账号矩阵源码关键词开发技术 如何提高 抖音 搜索排名&#xff1f;如何优化抖音搜索排名&#xff1f; 部分代码分析&#xff1a;场景&#xff1a;创建一个Tree()函数来实现以下特性&#xff0c;当我们需要时&#xff0c;所有中间对象 branch1、branch2 和 bra…

Mysql常见的索引模型

目录 有序数组哈希表二叉搜索树B-TreeBTree 有序数组 我们指定一个列为索引&#xff0c;然后按照这个列的值排序&#xff0c;以有序数据存放入数据表中&#xff0c;如下所示 这样&#xff0c;我们在查找数据的时候&#xff0c;就可以通过id这个列&#xff0c;在数据表中进行二…

阿里 P8 整理的《百亿级并发系统设计》实战手册,实在是太香了

面试官问&#xff1a;如何设计一个高并发系统&#xff1f; 说实话&#xff0c;如果面试官问你这个题目&#xff0c;那么你必须要使出全身吃奶劲了。为啥&#xff1f;因为你没看到现在很多公司招聘的 JD 里都是说啥有高并发经验者优先。 如果你确实有真才实学&#xff0c;在互…

9-《数据结构》

[TOC](9-《数据结构》 一、数组1.稀疏数组 二、链表三、队列四、栈五、树5.1 完全二叉树5.2 满二叉树&#xff1a;深度为k且有2^k-1个结点的二叉树称为满二叉树**5.3 二叉排序树&#xff08;二叉搜索树、二叉查找树&#xff09;5.4 平衡二叉树&#xff1a;5.5 红黑树 六、堆七、…

文件夹加密码的方法有哪些?文件夹加密方法盘点

在我们使用电脑的过程中&#xff0c;我们会将一些重要的数据放入文件夹内进行统一管理&#xff0c;为了保护数据安全&#xff0c;文件夹加密码通常是一个不错的选择。那么文件夹该怎么加密码呢&#xff1f;电脑文件夹加密码的方法有哪些呢&#xff1f; 文件夹加密码方法 首先…

opencv读取图片

opencv是一款非常强大的图像处理库&#xff0c;可以用来进行图像的处理。opencv库提供了丰富的工具&#xff0c;比如图像缩放&#xff0c;旋转&#xff0c;倾斜校正&#xff0c;自动对齐等等&#xff0c;使用这些工具可以很方便的进行图像的处理。那么你知道 opencv怎么读取图片…

Java学习笔记-04

目录 静态成员 mian方法 多态 抽象类 接口 内部类 成员内部类 静态内部类 方法内部类 匿名内部类 静态成员 static关键字可以修饰成员方法&#xff0c;成员变量被static修饰的成员&#xff0c;成员变量就变成了静态变量&#xff0c;成员方法就变成了静态方法static修…

Java流程控制(一)

⭐ 控制语句⭐ 条件判断结构(选择结构)⭐ switch 语句 做任何事情事情都要遵循一定的原则&#xff0c;毕竟不以规矩&#xff0c;不成方圆&#xff0c;例如&#xff0c;到图书馆去借书&#xff0c;就必须要有借书证&#xff0c;并且借书证不能过期&#xff0c;这两个条件缺一不可…

【新星计划回顾】第二篇学习计划-通过定义变量简单批量模拟数据

&#x1f3c6;&#x1f3c6;又到周末&#xff0c;最近这段时间非常忙&#xff0c;虽然导师首次参与新星计划活动已经在4月16日圆满结束&#xff0c;早想腾出时间来好好整理活动期间分享的知识点。 &#x1f3c6;&#x1f3c6;非常感谢大家的支持和活动期间的文章输出&#xff0…

软件测试基础(V模型W模型)

软件测试基础 1. 软件测试的生命周期 需求分析&#xff1a;站在用户的角度查看需求逻辑是否正确&#xff0c;是否符合用户的需求和行为习惯。站在开发的角度思考需求是否可以实现&#xff0c;或者说实现起来难度高不高测试计划&#xff1a;指定测试计划&#xff08;包括不限于…

Linux用户和组管理

1、用户和组简介 Linux 是多用户多任务操作系统。换句话说&#xff0c;Linux 系统支持多个用户在同一时间内登陆&#xff0c;不同用户可以执行不同的任务&#xff0c;并且互不影响。不同用户具有不问的权限&#xff0c;毎个用户在权限允许的范围内完成不同的任务。 用户组是具…

进攻即是最好的防御!19个练习黑客技术的在线网站

前言 进攻即是最好的防御&#xff0c;这句话同样适用于信息安全的世界。这里罗列了19个合法的来练习黑客技术的网站&#xff0c;不管你是一名开发人员、安全工程师、代码审计师、渗透测试人员&#xff0c;通过不断的练习才能让你成为一个优秀安全研究人员。以下网站希望能给各…