【排序】简单排序(C语言实现)


前言

我们常见的排序算法有下面这几种:
在这里插入图片描述
在这几种算法中,有比较简单的,有也比较复杂的,所以这里我打算把简单的排序整合成一篇文章,比较难的我会单独写成一篇

我们在讲排序算法的时候会提到稳定性的概念。
⭐️:这里提一下:
排序稳定性的意义:相同的数据排序后,相对位置是否发生变化


1. 冒泡排序


1.1 冒泡排序的思想

冒泡排序的基本思想:冒泡排序是交换排序中一种简单的排序方法。是许多人学的第一个排序算法,因此它也是我们的白月光。

它的基本思想是对所有相邻的数值进行比效,如果(a[j]>a[j+1]),则将其交换,最终达到有序化。

1.2 冒泡排序的实现

在这里插入图片描述

  1. 冒泡排序是一种非常理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

我们用代码来实现一下它的逻辑:

void BubbleSort(int* arr, int n)
{
	if (arr == NULL)
		return; // 防止使用的人传空指针过来
	for (int i = 0;i < n - 1;i++)
	{
		int flag = 1; // 定义一个变量来优化冒泡排序
		for (int j = 0;j < n - 1 - i;j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				// 我们在每次交换完数据以后都会将flag赋值为0,
				// 如果有一次我们比较完一趟下来都没有发生交换
				// 则表明该数组已经有序了
				flag = 0;
			}
		}
		if (flag == 1) 
			break; // 有序就直接退出循环了,不用继续将所有的数据都比较完
	}
}


2. 直接插入排序


2.1 直接插入排序的思想

直接插入排序是一种简单的插入排序法,其基本思想是:
  把待排序的数按照大小逐个比较,然后插入到一个已经排好序的有序序列中,直到所有的数插入完为止,得到一个新的有序序列。 当然你也不止可以比较数,也可以比较其他类型的数据,通过你想比较的方式区进行实现,因为这里我们说排序,所以就用整型来实现,会更加通俗易懂。

在实际生活中,我们玩扑克牌时,就用到了插入排序的思想:我们拿到排后,会将牌按一定的顺序调整好,在这个过程中,我们整理一张牌时,眼睛会将手里的牌看一遍,然后在将要插入的牌插入到合适的位置。

在这里插入图片描述

2.2 直接插入排序的实现

说了这么多我们来看一下直接插入排序具体是这样排序的吧:
  当插入第i(i >= 1)个元素时,前面的array[0], array[1], …, array[i - 1]已经排好序,此时用array[i]的排序码与array[i - 1], array[i - 2], …的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

在这里插入图片描述

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),他是一种稳定的排序算法
  4. 稳定性:稳定

我们用代码来实现这一逻辑:

void InsertSort(int* arr, int n)
{
	if (arr == NULL)
		return; // 防止使用的人传空指针过来
	for (int i = 0;i < n - 1;i++)
	{
		int tmp = arr[i + 1]; // 保存每趟的最后一个数
		int j = 0;
		for (j = i;j >= 0;j--)
		{
			if (tmp < arr[j]) // 如果最后一个数小于前一个数就将前一个数往后移
			{
				arr[j + 1] = arr[j];
			}
			else // 由于我们是从前面开始排序的,所以能够保证前面是有序的,所以只要我们比较的这个数大于它的前一个,我们就在当前位置插入
				break;
		}
		arr[j + 1] = tmp; // 在arr[j]位置插入tmp
	}
}


3. 选择排序


3.1 选择排序的基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

3.2 选择排序的实现

  • 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
  • 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

在这里插入图片描述

选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

插入排序的思想如上面所述,我们是先遍历一遍,找到数组中最小的那个数,然后将它放到最左边。但其实我们可以再给这个排序进行一些优化:我们每次遍历可以分别找到它的最小值和最大值,然后再将最小值和最大值分别放到最左边和最右边。具体代码实现如下:

// 我们需要一个交换值的函数
void swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

void SelectSort(int* arr,int n)
{
	if (arr == NULL)
		return; // 防止使用的人传空指针过来
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		// 这里选择用下标来记录数组中的最大值最小值的位置,
		// 这样下面做交换的时候会比较方便,
		// 如果我们直接用max/min来记录最大值/最小值后续交换的时候就会很困难
		int maxi = left, mini = left; // 从左边开始找最大值和最小值
		
		for (int i = left;i <= right;i++)
		{
			// 每趟找到最大的数和最小的数
			if (arr[i] > arr[maxi])
				maxi = i;
			if (arr[i] < arr[mini])
				mini = i;
		}
		swap(&arr[left], &arr[mini]);
		if (left == maxi) 
		{
			// 这步是防止如果maxi是left,
			// 如果maxi是left,left和mini交换以后,maxi指向的数就被换了位置了
			maxi = mini;
		}
		swap(&arr[right], &arr[maxi]);

		//把最小的和最大的分别放到了最左边和最右边,left和right就要分别往中间靠
		left++;
		right--;
	}
}


4. 计数排序


4.1 计数排序的思想

计数排序它是一个性能比较高,但使用范围很局限(仅适用于整型范围较集中的数进行比较)的排序,这里学习计数排序主要还是学习它的思想,它的思想也可以解决一些其他的问题。

计数排序的思想:创建一个数组(range(范围)是要比较的数中最大值和最小值的差值,也就是这组数的极差)用来记录我们要排序的数组中的各个数据出现的次数,然后再将我们记录的数据(所对应的数出现的次数)依次赋值回我们要排序的数组中。在这里就可以看出来,为什么适用于范围较集中的数据用来排序了。

4.2 计数排序的实现

  • 只适用于整型且范围较集中的数
  • 时间复杂度:O(N + range)
  • 空间复杂度:O(range)
  • 不用讨论它的稳定性

请添加图片描述
我们先统计一下arr数组中每个数出现的次数,对应到下标存入到counst
请添加图片描述
然后又在counst中下标对应的数每次赋值到arr数组中。

但到了这里,我们发现,如果是排序 101 101 101到之间的数 109 109 109,我们显然开一块大小为 110 110 110的数组显然是不现实的,因为前面的空间我们也用不到,这样就造成了浪费。

我们可以用一些手段来调整一下:
  先遍历一遍要排序的数组,找到它的最大值和最小值,然后计算出他们之间的范围(range),知道了他们之间的范围,就可以开辟这块空间了。然后再将我们要排序的数组中的数,以一定的方式映射到counst中。
在这里插入图片描述
这样一来我们就可以只开辟大小为9的空间。(range = max-min+1,+1的原因是最大值和最小值都是左闭右闭的区间,相减之后得到的只是它们相隔的距离,+1才能得到它们之间的距离)

我们用代码实现一下这个逻辑:

void CountSort(int* arr, int n)
{
	if (arr == NULL)
		return; // 防止使用的人传空指针过来
	int max = arr[0], min = arr[0];
	for (int i = 0;i < n;i++)
	{
		if (arr[i] < min)
			min = arr[i];
		if (arr[i] > max)
			max = arr[i];
	}
	// 找到最大的数和最小的数来确定范围
	int range = max - min + 1;
	// 开辟一个数组用来计数a数组中每个数据出现了多少次
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)// 处理一下开辟空间失败的结果
	{
		perror("malloc fail");
		exit(-1);
	}
	// 将我们新创建的数组初始化为0
	memset(count, 0, sizeof(int) * range);

	// 遍历一遍a数组,将它的值出现的次数存入到count中
	for (int i = 0;i < n;i++)
	{
		count[arr[i] - min]++;
	}
	// 排序
	int j = 0;
	for (int i = 0;i < range;i++)
	{
		while (count[i]--)
		{
			arr[j++] = i + min;
		}
	}

	// 用完开辟的空间要记得释放,否则容易造成内存泄漏
	free(count);
	count = NULL;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hyt的笔记本

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值