排序03(数据结构初阶)

发布于:2025-02-27 ⋅ 阅读:(17) ⋅ 点赞:(0)

欢迎大家来到我的博客,很高兴又与大家见面,给生活加点imprtus,开启今天的编程之路!!
在这里插入图片描述
今天我们来给排序收个尾,了解基础哈希算法,感受递归魅力!!
欢迎点赞关注!!

排序

归并排序

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

递归版本

归并排序核心步骤。我们先来画图演示一下:
在这里插入图片描述
在这里插入图片描述
我们可以得出:
数组只剩一个元素时,此时左端点等于右端点 ,如果我们设置left和right指针的话,就等价于left==right,此时数组一定有序。
而且,因为函数递归的时候会不断创建函数栈帧,当递归结束之后,函数中会保存着对应的区间范围,我们利用这个特性再来进行合并

思路:确定中间值,随后分为两个区间,直到区间只剩一个元素,可以认为每一数组是有序的,分解之后来合并两个序列到数组tmp中,随后再讲将tmp赋值给原数组

代码实现

在这里插入图片描述

参数初始化left=0,right=n-1,tmp就是新创建的数组,最后要赋值给原数组

递归部分写完了,我们再来看合并部分:
这里我们可以来画图模拟一下思路:
在这里插入图片描述

为什么这里需要设置begin1,begin2,end1,en2?
保留left指针的原本的属性,为了之后赋值做准备

来看代码:

	//合并两个序列:[left,mid] [mid+1,right]
	int begin1 = left, end1 = mid;//这样可以保证left保存的数值不变
	int begin2 = mid + 1, end2 = right;
	int index = begin1;

	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])//更小的数据入数组,同时对应的位置指针向后遍历
		{
			tmp[index++] = arr[begin1++];
		}
		else {
			tmp[index++] = arr[begin2++];
		}
	}
	//左序列数据没有全部放到tmp数组中
	//右序列数据没有全部放到tmp数组中
	while (begin1 <= end1)//因为我需要循环一直放入,所以我需要使用循环语句而不是判断语句
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//我们成功将正确顺序的数组给到tmp数组之后,我们将tmp数组的顺序赋值给原数组
	for (int i = left;i <= right;i++)
	{
		arr[i] = tmp[i];
	}
}

赋值的时候为什么这里int i=left?
当函数中递归部分结束的时候,此时left和right的信息这一个元素的函数栈帧中仍然保留的,如果让i=0,会造成0这个位置的数据一直变动

这种方法存在递归,而且类似于二叉树,会一直二分元素个数

含递归算法的时间复杂度:递归次数*每次递归的时间复杂度

时间复杂度:O(n*logn)
空间复杂度:O(n+Range)因为我不清楚n于Range谁更大

非递归版本

在非递归版本中,我们直接对数组进行操作,因为递归版本递归结束后存储着left和right信息,所以能够找到对应的区间,非递归时,我们需要设置gap变量来寻找left和right信息。
我们看一下下方图解:
在这里插入图片描述
我们先来说明思路:

创建新的数组tmp,是为了来放置排序好的数组元素->以每一次的gap来合并两个有序数组->导入到原数组

void MergeSortNonR(int* arr, int n)
{
	//申请相同数组大小,按gap分组并一直比较并一直会赋值
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("mmalloc failed!!");
		exit(1);
	}
	//申请成功
	int gap = 1;//一开始只有一个元素为一组
	while (gap<n)//当gap==n时,此时只有一组,就没有必要继续比较了,直接退出循环
	{
		//gap为固定值时,按组对所有数据进行操作
		//根据gap分组,两两一合并
		for (int i = 0;i < n;i += 2 * gap)//对i变量的操作有点没懂
		//对变量i进行操作,因为我每次我都要跨过一组两个区间,例如当gap=1时,要一次性跨过2个元素,就是2*gap个元素
		{
			int begin1 = i, end1 = i + gap - 1;//gap代表每组的元素个数,i+这组包含元素个数,随后再利用-使得右区间对得上
			//第二个范围的左区间就是end1+1
			int begin2 = i + gap, end2 = i + gap + gap - 1;
			int index= begin1;
			//两个有序序列进行合并
			while (begin1 <= end1 && begin2 <= end2)
			{
				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++];
			}
			//导入到原数组
			memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
			//例如,做序列10[0,0],右序列6[1,1],则此时需要拷贝两个数据,并且此时i=0
		}
		gap *= 2;//合并之后,每组元素个数呈2倍增长
	}
}

细节

1:对变量i进行操作,因为我每次我都要跨过一组两个区间,例如当gap=1时,要一次性跨过2个元素,就是2*gap个元素
2:做序列10[0,0],右序列6[1,1],则此时需要拷贝两个数据,并且此时i=0,所以拷贝end2-i+1个元素
3:我是要对对应组进行排序,所以需要begin1的位置开始放入数据,index初始化为begin1

代码实现

这里我们需要说明一下细节:

随着gap的增加,有没有可能没有右序列或者有序列不全的

我们来看下方这张图:
左序列存在,右序列不存在或者左序列未满gap个元素
在这里插入图片描述
我们直接跳过这一次循环
再来看一张图:
左右序列都存在,但是右序列未满
在这里插入图片描述
直接将end2重置为最后一个元素的下标
最后应该在初始化begin1,begin2,end1,end2之后加上这句代码:

			if (begin2 >= n)//没有右序列,此时只有左边的组,例如:左[2,3],右没有,或者左边的数据个数没有满
			{
				break;//此时就直接进入下一个gap了
			}
			if (end2 >= n)//右序列不满gap个数据
			{
				end2 = n - 1;//左序列和右序列都有个数,但是,右序列不全,左序列完全
				//此时n-1就可以直接走到最后一个元素的下标
			}

非比较排序–计数排序

2.6.1 计数排序:
计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应用。操作步骤:
1:统计相同元素出现次数
2:根据统计的结果将序列回收到原来的序列中
我们来画一个图说明一下:
在这里插入图片描述
那么我们应该如何确定这个count数组的大小呢?
首先最简单的,也是比较容易想到的,就是直接最大值+1,因为我需要保证每个元素在count数组中都有对应的下标
不妨我们再来举例:
在这里插入图片描述
当我们数值过大时,就会造成大量的空间浪费,如果是这种情况,我们应该怎么办呢?
在这里插入图片描述
但是这样也不是万能的,如果说min和max数值相差过大,仍然会造成大量空间浪费
所以我们可以得出

计数排序的特性:
计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。

代码实现

思路:找出数组的最值,之后创建新的count计数数组,最后计数,再遍历count数组求得原数组的顺序

void CountSort(int* arr, int n)
{
	//寻找最值
	int min = arr[0], max = arr[0];
	for (int i = 1;i < n;i++)//因为0已经被设置为了两个最值,所以这里我们使用1开始遍历
	{
		if (arr[i] < min)
		{
			min = arr[i];
		}
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}
		//根据最大值来创建数组空间
		int Range = max - min + 1;
		int* count = (int*)malloc(sizeof(int) * Range);
		if (count == NULL)
		{
			perror("malloc falied!!");
			exit(1);//退出码为1
		}
		//初始化count数组
		memset(count, 0, sizeof(int) * Range);//memset()函数用于将一段内存区域设置为指定的值
		//直接用calloc也行,创建的时候就直接初始化为0了
		//int* count = (int*)calloc(Range, sizeof(int));
		//if (count == NULL)
		//{
		//	preeor("calloc falied!!");
		//	exit(1);
		//}

		//计数
		for (int i = 0;i < n;i++)
		{
			count[arr[i] - min]++;
		}
		//直接根据哈希表来打印数组,同时赋值给原数组
		int index = 0;
		for (int i = 0;i < Range;i++)
		{
			//数据出现的次数count[i]
			//下标———源数据 i+min
			while (count[i]--)
			{
				arr[index++] = i + min;
			}
		}
}

结语

感谢大家阅读我的博客,不足之处欢迎指正交流!!
劝君莫惜金缕衣,劝君惜取少年时!!加油!!
在这里插入图片描述