排序03(数据结构初阶)

文章目录

欢迎大家来到我的博客,很高兴又与大家见面,给生活加点imprtus,开启今天的编程之路!!

今天我们来给排序收个尾,了解基础哈希算法,感受递归魅力!!

欢迎点赞关注!!

排序

归并排序

归并排序算法思想:

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

递归版本

归并排序核心步骤。我们先来画图演示一下:

我们可以得出:

数组只剩一个元素时 ,此时左端点等于右端点 ,如果我们设置left和right指针的话,就等价于left==right ,此时数组一定有序。

而且,因为函数递归的时候会不断创建函数栈帧,当递归结束之后,函数中会保存着对应的区间范围,我们利用这个特性再来进行合并

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

代码实现

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

递归部分写完了,我们再来看合并部分:

这里我们可以来画图模拟一下思路:

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

来看代码:

c 复制代码
	//合并两个序列:[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来合并两个有序数组 ->导入到原数组

c 复制代码
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之后加上这句代码:

c 复制代码
			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数组求得原数组的顺序

c 复制代码
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;
			}
		}
}

结语

感谢大家阅读我的博客,不足之处欢迎指正交流!!
劝君莫惜金缕衣,劝君惜取少年时 !!加油!!

相关推荐
一只自律的鸡18 分钟前
C进阶 自定义类型
c语言·开发语言
2403_875180952 小时前
AI数字人开发,引领科技新潮流
java·大数据·数据结构·人工智能·科技·前端框架
JKHaaa3 小时前
单链表的排序(C++)
数据结构·c++·算法
nqqcat~3 小时前
C语言机试编程题
c语言·开发语言·考研·算法
上课耽误学习4 小时前
补题A-E Codeforces Round 953 (Div. 2)
数据结构·计算几何·容斥原理
byte轻骑兵5 小时前
【嵌入式Linux应用开发基础】网络编程(4):UDP协议
linux·c语言·嵌入式软件开发
binbinxyz6 小时前
【算法系列】快速排序详解
python·算法·排序算法
小贾要学习6 小时前
【C++】模板
c语言·开发语言·c++
wheeldown8 小时前
【蓝桥杯】每天一题,理解逻辑(1/90)【Leetcode 移动零】
c语言·leetcode·职场和发展·蓝桥杯