数据结构排序(四)

༺ 个人主页 · 纪念229 ༻

🏠我的博客主页🏠

༒专栏目录:《数据结构》༒

༒专栏目录:《算法》༒

༒专栏目录:《MySQL数据库》༒

༒专栏目录:《前端开发》༒

༒其它有趣的计算机知识༒

༺世上本没有路,走的人多了自然就有了༻


这篇文章讲述的是归并排序和计数排序,希望对你有所帮助

文章目录


1.归并排序

归并排序就是将数组二分到不能再分然后将其排序后合并(每两个序列一组)

代码展示

c 复制代码
//归并排序
void MergeSort(int* arr, int n);
c 复制代码
//归并排序
void _MergeSort(int* arr, int left, int right, int* tmp)
{
	//二分
	//如果left>=二分结束
	if (left >= right)
	{
		return;
	}
	//[left,right]
	int mid = (left + right) / 2;
	//根据mid分为左右两个序列:[left, mid] [mid + 1, right]
	//这里利用_MergeSort递归不只是二分
	//还有给该序列数组进行排序和子序列合并(同理它的递归函数也会这样做保证子序列是排序好的)
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);

	//合并两个有序序列
	//排序后合并
	//利用begin1 、end1 、begin2 、end2 、index
	//根据循环判断将排序好的数据放入tmp数组中
	//最后根据序列把数据放入数组arr所对应的下标中的数据里
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	//为啥不是int index = 0;
	//因为不只是这个函数要合并两个有序序列
	//递归函数也有要用这样才能保证合并的两个序列是有序序列
	int index = begin1;
	//两个序列从begin1(2)开始比较小的放tmp数组上
	//直到某个序列越界(越界后没排完的接着用while排到tmp数组中)
	//while循环范围是begin1 <= end1 && begin2 <= end2
	while (begin1 <= end1 && begin2 <= end2)
	{
		//为啥是arr[begin1] <= arr[begin2]
		//不是arr[begin1] < arr[begin2]
		//为了保证arr数组的稳定性当两序列数据相等时将第一个序列的数据放入tmp数组上
		//稳定性给个数组[1,5,3,4,4,2]
		//                     x y
		//具有稳定性的排序后 [1,2,3,4,4,5]
		//                          x  y
		//不具有稳定性的排序后 [1,2,3,4,4,5]
		//                            y x 
		//就是虽然排序成功了但是原来前面的的4放到了后面这叫不稳定性
		if (arr[begin1] <= arr[begin2])
		{
			tmp[index++] = arr[begin1++];
		}
		else {
			tmp[index++] = arr[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//最后根据序列范围将tmp中的数据放入arr中
	for (int i = left; i <= right; i++)
	{
		arr[i] = tmp[i];
	}
}
//归并排序就是先数组二分分到不能再分
//然后再每个小数组(数组数据个数相同)排序后合并(其实就是根据下标合并)
//利用tmp将排序好的数组按照序列下标放入对应的arr的下标上的数据上
void MergeSort(int* arr, int n)
{
	//利用tmp将排好的数据放入对应的数组序列内
	int* tmp = (int*)malloc(sizeof(int) * n);
	//函数的使用不用写类型名
	_MergeSort(arr, 0, n - 1, tmp);
	//最后将创建的空间free
	free(tmp);
	//给tmp地址赋为null
	tmp = NULL;
}

具体讲解:

int* tmp = (int*)malloc(sizeof(int) * n);

首先我们要创建一个tmp指针该指针里有与原数组相同的空间

目的是将比较较小的数据放入tmp数组中最后实现排序将排序好的数据按照序列给原数组赋值

c 复制代码
//归并排序
void _MergeSort(int* arr, int left, int right, int* tmp)
{
	//二分
	//如果left>=二分结束
	if (left >= right)
	{
		return;
	}
	//[left,right]
	int mid = (left + right) / 2;
	//根据mid分为左右两个序列:[left, mid] [mid + 1, right]
	//这里利用_MergeSort递归不只是二分
	//还有给该序列数组进行排序和子序列合并(同理它的递归函数也会这样做保证子序列是排序好的)
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);

	//合并两个有序序列
	//排序后合并
	//利用begin1 、end1 、begin2 、end2 、index
	//根据循环判断将排序好的数据放入tmp数组中
	//最后根据序列把数据放入数组arr所对应的下标中的数据里
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	//为啥不是int index = 0;
	//因为不只是这个函数要合并两个有序序列
	//递归函数也有要用这样才能保证合并的两个序列是有序序列
	int index = begin1;
	//两个序列从begin1(2)开始比较小的放tmp数组上
	//直到某个序列越界(越界后没排完的接着用while排到tmp数组中)
	//while循环范围是begin1 <= end1 && begin2 <= end2
	while (begin1 <= end1 && begin2 <= end2)
	{
		//为啥是arr[begin1] <= arr[begin2]
		//不是arr[begin1] < arr[begin2]
		//为了保证arr数组的稳定性当两序列数据相等时将第一个序列的数据放入tmp数组上
		//稳定性给个数组[1,5,3,4,4,2]
		//                     x y
		//具有稳定性的排序后 [1,2,3,4,4,5]
		//                          x  y
		//不具有稳定性的排序后 [1,2,3,4,4,5]
		//                            y x 
		//就是虽然排序成功了但是原来前面的的4放到了后面这叫不稳定性
		if (arr[begin1] <= arr[begin2])
		{
			tmp[index++] = arr[begin1++];
		}
		else {
			tmp[index++] = arr[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//最后根据序列范围将tmp中的数据放入arr中
	for (int i = left; i <= right; i++)
	{
		arr[i] = tmp[i];
	}
}
//归并排序就是先数组二分分到不能再分
//然后再每个小数组(数组数据个数相同)排序后合并(其实就是根据下标合并)
//利用tmp将排序好的数组按照序列下标放入对应的arr的下标上的数据上

void _MergeSort(int* arr, int left, int right, int* tmp)

arr指的是原数组的指针 left刚开始是0,right刚开始是n - 1(数组最后一个下标数)

c 复制代码
if (left >= right)
{
	return;
}
//[left,right]
int mid = (left + right) / 2;
//根据mid分为左右两个序列:[left, mid] [mid + 1, right]
//这里利用_MergeSort递归不只是二分
//还有给该序列数组进行排序和子序列合并(同理它的递归函数也会这样做保证子序列是排序好的)
_MergeSort(arr, left, mid, tmp);
_MergeSort(arr, mid + 1, right, tmp);

二分就是将序列中初下标和末下标之和再整除2

递归的作用就是将二分的序列分到序列不再含有二个以上的数据(递归还有一个作用后面会讲将有序序列合并)

c 复制代码
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int index = begin1;

我们得到二分值时得到两个序列left,mid mid + 1,right

通过比较begin1与begin2出来的较小的值利用index遍历放入数组tmp中

这里明明tmp是指针我们却喊它数组

原因跟数组类比arr(我们称之为首元素的地址)

c 复制代码
while (begin1 <= end1 && begin2 <= end2)
{
	//为啥是arr[begin1] <= arr[begin2]
	//不是arr[begin1] < arr[begin2]
	//为了保证arr数组的稳定性当两序列数据相等时将第一个序列的数据放入tmp数组上
	//稳定性给个数组[1,5,3,4,4,2]
	//                     x y
	//具有稳定性的排序后 [1,2,3,4,4,5]
	//                          x  y
	//不具有稳定性的排序后 [1,2,3,4,4,5]
	//                            y x 
	//就是虽然排序成功了但是原来前面的的4放到了后面这叫不稳定性
	if (arr[begin1] <= arr[begin2])
	{
		tmp[index++] = arr[begin1++];
	}
	else {
		tmp[index++] = arr[begin2++];
	}
}
while (begin1 <= end1)
{
	tmp[index++] = arr[begin1++];
}
while (begin2 <= end2)
{
	tmp[index++] = arr[begin2++];
}
//最后根据序列范围将tmp中的数据放入arr中
for (int i = left; i <= right; i++)
{
	arr[i] = tmp[i];
}

while (begin1 <= end1 && begin2 <= end2)

循环运行的条件是begin1 <= end1 && begin2 <= end2意思是有一个序列的元素比完就结束

c 复制代码
if (arr[begin1] <= arr[begin2])
{
		tmp[index++] = arr[begin1++];
}
else {
		tmp[index++] = arr[begin2++];
}

里面就是比较俩个序列的下标begin上的值那个小就把begin下标上的数据放入数组tmp中然后将idex++和对应的begin++

arr[begin1] <= arr[begin2]

这里讲一下这个意思是如果序列1的某数据与序列2的某数据相等那么优先将序列1的数据放入tmp数组中

c 复制代码
while (begin1 <= end1)
{
	tmp[index++] = arr[begin1++];
}
while (begin2 <= end2)
{
	tmp[index++] = arr[begin2++];
}

当第一个循环结束后肯定还有序列中的数据没赋值到tmp数组中那些数据就是最大的数据且也是排好的(原因是递归是从序列数据中只有一个元素开始合并排序的到了后面序列里数据变多并且都是排好的)

将它们放入循环用的就是以上两个循环若是某个序列都放入tmp中也就是begin > end就跳过若是都为begin > end说明排序完成

c 复制代码
for (int i = left; i <= right; i++)
{
	arr[i] = tmp[i];
}

最后根据序列范围将tmp数组的数据(已排好的数据)都放入原数组中

c 复制代码
	free(tmp);
	//给tmp地址赋为null
	tmp = NULL;

最后将tmp指针的空间free将指针变量tmp置为NULL

2.计数排序

计数排序就是先找到原数组的最小值与最大值将其相减找出其数据范围创建一个数组(大小为最大值~最小值之间的个数)将这个范围内的原数组数据的数据按照数据出现的个数一一排列出来(具体的值计的是出现元素的个数没出现的值为0下标加最小值就是该值的真实大小),最后根据这个创建的数组将数据从小到大排入原数组中

代码展示

c 复制代码
//计数排序
void CountSort(int* arr, int n);
c 复制代码
void CountSort(int* arr, int n)
{
	//找最大值和最小值
	//假设最大值最小值都为arr[0],所以只要和下标1到n - 1比较
	int min = arr[0], max = arr[0];
	for (int i = 1; i < n; i++)
	{
		if (arr[i] < min)
		{
			min = arr[i];
		}
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}
	//利用最大值和最小值求出range(所需计数个数以及范围)创造所需范围的计数数组count
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	//将计数数组都赋值为0
	//memset(指针, 赋值为啥, 所需字节个数)
	//memset作用:将数组中的数据赋值为我们要求的数(只适用于字符串数组)其它只适用于赋值为0或-1
	//头文件为#include<string.h>
	memset(count, 0, sizeof(int) * range);
  //将arr中的数据计数到count中没有的话就为0有一个就加加
	//下标由arr[i] - min 所得
	for (int i = 0; i < n; i++)
	{
	  count[arr[i] - min]++;
	}
	//将count数组还原到原数组中使其有序
	//count数组中的数据和加起来就是原数组数据的个数
	//创建一个index作为下标遍历原数组
	int index = 0;
	for (int i = 0; i < range; i++)
	{
	//如果该下标数据不为0说明有数据
	//就将该下标加上min(就是原数组上的数据) 赋值到数组中
	//每循环一次就将count下标上的数据减减(直到为0为止)arr接收后下标加加继续接收之后的数据
		while (count[i]--)
		{
			arr[index++] = i + min;
		}
 }
}

具体讲解:

c 复制代码
int min = arr[0], max = arr[0];
for (int i = 1; i < n; i++)
{
	if (arr[i] < min)
	{
		min = arr[i];
	}
	if (arr[i] > max)
	{
		max = arr[i];
	}
}

首先找到数组中的最大值和最小值开始首先默认arr0为最大值和最小值然后根据下标i遍历如果arri < min 那么min = arri;如果arri > max

那么max = arri;

c 复制代码
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)
{
	perror("malloc fail");
	exit(1);
}

将最大值与最小值相减 再减1得出最大值与最小值之间的个数range(包括它们自己)

然后根据range创造count数组

memset(count, 0, sizeof(int) * range);

memset

功能:设置数组内部数据用于将数据全设置为0/-1(任何类型数组通用)

改变字符串数组中的数据(比如全设为1 ,4,x,o)

memset(指针, 赋值为啥, 所需字节个数)

代码里的意思是将数组里的数据全部设为0

c 复制代码
for (int i = 0; i < n; i++)
{
  count[arr[i] - min]++;
}

利用循环遍历原数组中所有元素

arr[i] - min

arri - min得到的值就是count数组的下标,假如遍历到原数组的最小值 然后减min就是0最大就是下标最大就是range + 1,它会把原数组的数据转化为下标下标出现几次就加几次在下标范围内没出现过的就为0

我们利用下标从小到大特性将count数组下标转化为它原本的值下标 + min放入原数组中然后遍历count数组将下标上不为0的把它的实际值赋值到arr中放一个减一个直到该下标上的数据减到0为止然后接着遍历其它下标直到i 不符合 i < range为止这时说明我们已经排好序了

c 复制代码
int index = 0;
for (int i = 0; i < range; i++)
{
//如果该下标数据不为0说明有数据
//就将该下标加上min(就是原数组上的数据) 赋值到数组中
//每循环一次就将count下标上的数据减减(直到为0为止)arr接收后下标加加继续接收之后的数据
	while (count[i]--)
	{
		arr[index++] = i + min;
	}
}

int index = 0;

利用index遍历arr数组每放一个值index就加加

c 复制代码
for (int i = 0; i < range; i++)
{
//如果该下标数据不为0说明有数据
//就将该下标加上min(就是原数组上的数据) 赋值到数组中
//每循环一次就将count下标上的数据减减(直到为0为止)arr接收后下标加加继续接收之后的数据
	while (count[i]--)
	{
		arr[index++] = i + min;
	}

利用for循环遍历count数组循环内利用while循环条件是counti不为0(使用后减减)就遍历将当前下标转化为实际值 i + min赋值到原数组中之后index++就这样直到下标 i 不符合 i < range循环结束此时原数组排序完毕


数据结构排序到此就告一段落,希望对你有所帮助,感谢观看!

相关推荐
独立开发之道17 分钟前
【three.js教程】Three.js 矩阵变换:position/quaternion/scale 和 matrix 到底怎么配合
开发语言·javascript·矩阵
IvanCodes20 分钟前
Python 基础语法(四):循环结构与流程控制
开发语言·python
OPEN-F21 分钟前
C++工程化:编译链接、调试与性能优化
开发语言·c++
坐吃山猪34 分钟前
【多线程】什么时候必须/推荐使用Thread类
java·开发语言
一只小阿乐43 分钟前
java 快速上手开发 2
java·开发语言·前端
张宇Joaquin1 小时前
灵渠测试工具-urma_perftest
java·开发语言
高彬1 小时前
SAP ECC6.0 对接 C# web Services服务
开发语言·前端·c#·sap
张小姐的猫1 小时前
【C++开发脚手架】 —— Jsoncpp上手
java·linux·开发语言·网络·c++·tcp/ip·udp
一晌小贪欢1 小时前
python-第20天:关键字参数与不定长参数
java·开发语言·前端·python·数据可视化·函数参数·python办公