当前位置: 首页 > article >正文

【初阶数据结构】星河中的光影 “排” 象:排序(上)

文章目录

  • 1.排序的概念及分类
  • 2.插入排序
    • 2.1 直接插入排序(InsertSort)
    • 2.2 希尔排序(ShellSort)
  • 3.选择排序
    • 3.1 直接选择排序(SelectSort)
    • 3.2 堆排序(HeapSort)
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

本章节是初阶数据结构最后一个部分,排序在数据中的应用是特备常见的,一般在企业中的排序数据量成万上亿,所以选取一种合适且效率高的排序尤为重要

1.排序的概念及分类

排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作

常见的排序算法的可以按如图所示分类:

在这里插入图片描述

以下所有的排序算法均以升序的方式实现

2.插入排序

2.1 直接插入排序(InsertSort)

直接插入排序是一种简单的插入排序法,其基本思想是: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列

简单来说我们平常玩的扑克牌游戏就是一种插入排序

在这里插入图片描述

排序原理:

当插入第 i ( i >= 1 ) 个元素时,前面的 array[0]array[1]array[i-1] 已经排好序,此时用 array[i] 的排序码与 array[i - 1]array[i - 2] 的排序码顺序进行比较,找到插入位置即将 array[i] 插入,原来位置上的元素顺序后移

动图理解:

请添加图片描述

💻排序实现:

void InsertSort(int* a, int n)
{
	for (int i = 1; i < n; ++i)
	{
		int end = i - 1;
		int tmp = a[i];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

⌨️代码解读:

  1. 第一个 for 循环指的是从第一个数开始依次往后遍历,end 指向已排序部分的最后一个元素(即开始的 a[0] ),tmp = a[i] 提前保存该位置的数据,避免移动时的覆盖消除该数据。
  2. 从后往前遍历已排序部分,找到合适的插入位置,如果当前要插入的元素小于已排序部分的元素,将已排序部分的元素后移一位,--end 继续向前比较;如果找到合适的插入位置break 退出循环
  3. 找到合适位置后,a[end + 1] = tmp 将当前要插入的元素插入到合适的位置

🖱️复杂度分析:

时间复杂度:

最好情况: 数组已经是有序的,此时内层的 while 循环每次只需要比较一次就会退出,时间复杂度为 O(n)

最坏情况: 数组是逆序的,内层的 while 循环每次都需要比较到已排序部分的第一个元素,时间复杂度为 O(n²)

平均情况: 时间复杂度为 O(n²)

空间复杂度: O(1)

总结:元素集合越接近有序,直接插入排序算法的时间效率越高

2.2 希尔排序(ShellSort)

希尔排序又称缩小增量法其基本思想是: 先选定一个整数 gap ,把待排序文件中所有记录分成多个组,所有距离为 gap 的记录分在同一组内,并对每一组内的记录进行排序。然后,取新的 gap ,重复上述分组和排序的工作。当到达 gap = 1 时,所有记录在统一组内排好序

分组如图所示:

在这里插入图片描述

相同颜色的数字为同一组

💻排序实现:

版本1: 按组分配排序

void ShellSort(int* a, int n)
{
	int gap = n;
	for(int j = 0; j < gap; j++)
	{
		gap /= 2;
		for (int i = j; i < n - gap; i += gap)
		{
			int end = i;
			int tmp = a[i + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
				a[end + gap] = tmp;
			}
		}
	}
}

版本2: 依次排序

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap /= 2;
		//gap = n / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[i + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
				a[end + gap] = tmp;
			}
		}
	}
}

⌨️代码解读:

希尔排序其实和插入排序是十分相似的,希尔排序是对直接插入排序的优化,只不过是每间隔 gap 个数进行排序,插入排序间隔是 1 ,通过多次 gap 的改变,多次的分组排序最终能趋向于有序

🔥值得注意的是:

  1. for 循环中 i < n - gap 是因为排完序后剩下的数根据 gap 的大小,剩下的数已经自动有序了
  2. gap /= 2gap = n / 3 + 1 都是可以的,并没有严格的限制
  3. tmp 一直在 end 的下一个位置,插入的位置一直在 end 的下一个位置
  4. gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快
  5. 希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定

《数据结构(C语言版)》— 严蔚敏:

在这里插入图片描述

🖱️复杂度分析:

时间复杂度:

最好情况: 数组已经是有序的,时间复杂度为 O(n)

最坏情况: 数组是逆序的,时间复杂度为 O(n²)

平均情况: 时间复杂度大约为 O( n 1.3 n^{1.3} n1.3)

空间复杂度: O(1)

总结: 希尔排序是不稳定的

3.选择排序

3.1 直接选择排序(SelectSort)

直接选择排序可以说是最简单的一种排序了,其基本思想为: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完

排序原理:

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

动图理解:

请添加图片描述

💻排序实现:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void SelectSort(int* a, int n)
{
	int left = 0, right = n - 1;
	while (left < right)
	{
		int mini = left, maxi = right;
		for (int i = left; i <= right; ++i)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}

			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}

		Swap(&a[left], &a[mini]);
		//如果left和maxi重叠,交换后修正一下
		if (left == maxi)
		{
			maxi = mini;
		}
		Swap(&a[right], &a[maxi]);

		++left;
		--right;
	}
}

⌨️代码解读:

  1. mini 初始化为 leftmaxi 初始化为 right,用于记录当前未排序部分的最小值和最大值的索引
  2. 通过 for 循环从 leftright 遍历数组,比较每个元素与 a[mini]a[maxi] 的大小,如果发现更小的元素则更新 mini,如果发现更大的元素则更新 maxi
  3. 调用 Swap 函数将最小值 a[mini] 交换到左边界 a[left] 的位置,最大值 a[maxi] 交换到右边界 a[right] 的位置
  4. ++left--right 分别将左边界右移和右边界左移,缩小未排序部分的范围

🔥值得注意的是: 如果 left 恰好等于 maxi,说明在交换最小值时,最大值的位置已经被交换到了 mini 处,所以需要将 maxi 更新为 mini

🖱️复杂度分析:

时间复杂度:

最好情况: 即使数组已经有序,每一轮仍然需要遍历未排序部分来确定最小值和最大值的位置,时间复杂度为 O(n)

最坏情况: 当数组是逆序排列时,同样需要进行完整的遍历和交换操作,时间复杂度为 O(n²)

平均情况: 时间复杂度为 O(n²)

空间复杂度: O(1)

总结: 直接选择排序思考非常好理解,但是效率不是很好,实际中很少使用

3.2 堆排序(HeapSort)

堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆排降序建小堆

堆的专题章节已经有详细的分析了,这里就不过多讲解

传送门:【初阶数据结构】森林里的树影 “堆” 光:堆

总结: 堆排序使用堆来选数,效率就高了很多


希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述


http://www.kler.cn/a/566062.html

相关文章:

  • Octave3D 关卡设计插件
  • GaussDB SQL 调优:从执行计划到AI驱动的进阶指南
  • 【搜广推算法的力量:如何用数据驱动用户体验与商业价值?】
  • 1-5gcc
  • 【Electron入门】进程环境和隔离
  • 水仙花数Ⅰ
  • Windows 11 新增拖拽分享功能及开始菜单布局优化
  • 动态绑定属性和方法
  • unity pico开发二:创建基本的交互
  • BUG: 解决新版本SpringBoot3.4.3在创建项目时勾选lombok但无法使用的问题
  • pyQT5简易教程(一):制作一个可以选择本地图片并显示的桌面应用
  • 数据结构~哈希
  • 高频 SQL 50 题(基础版)| 排序和分组:2356. 每位教师所教授的科目种类的数量
  • 使用AoT让.NetFramework4.7.2程序调用.Net8编写的库
  • Mac 中与PyCharm 中的单步调试快捷键
  • 【江科大STM32】TIM输出比较-PWM功能(学习笔记)
  • 【JS】Web Worker知识和动态创建以及场景案例
  • YOLOv8 行人相关识别技术提升
  • P3372 【模板】线段树 1
  • 自用的vim脚本