[数据结构——Lesson11排序的概念及直接插入排序(还可以)]
目录
前言
学习目标
1排序的概念及其分类
1.1排序的概念
1.2常见的排序算法
直接插入排序
基本思想
具体步骤
代码分析与详解
①写出单趟的的插入过程
②利用循环控制每一趟插入排序
③完整的图例和代码演示
代码实现
编辑
时间复杂度分析
前言
排序是数据处理中不可或缺的基础操作,从日常的手机相册按时间排序、电商平台商品按销量筛选,到后台系统的日志按时间戳整理,排序算法的影子无处不在。它不仅是数据结构领域的核心知识点,更是面试中高频考察的内容 —— 面试官往往通过对排序算法的理解和实现,来判断开发者的逻辑思维与代码功底。
接下来,我们就从最贴近日常思维的一种排序方式入手,开启排序算法的学习之旅 —— 今天要探讨的,便是直接插入排序。
学习目标
- 排序的概念及其分类
- 直接插入排序的理解及其实现
1排序的概念及其分类
1.1排序的概念
🌟排序:所谓排序,就是使一串数据,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
🌟稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排 序算法是稳定的;否则称为不稳定的。
🌟内部排序:数据元素全部放在内存中的排序。
🌟外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
1.2常见的排序算法
直接插入排序
基本思想
例如我们平时打扑克牌吧,那么如何进行扑克牌的排序呢?
- 举个例子,比如我手中有红桃 6,7,9,10 这 4 张牌,已经处于升序排列:
- 这时候,我又抓到一张红桃 8,如何让手中的 5 张牌重新变成升序呢?
最简单的方式,就是在已经有序的 4 张牌中找到红桃 8 应该插入的位置,也就是 7 和 9 之间,把红桃 8 插进去:
- 就像玩牌一样,有一种排序算法也采用了类似的思想:维护一个有序区,把元素一个个插入有序区的适当位置,直到所有元素都有序为止。
- 这样的排序算法,被称为直接插入排序。
具体步骤
直接插入排序是一种简单的插入排序法,其基本思想是:
- 直接插入排序的基本思想是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
- 具体来说,就是将数组分为已排序和未排序两部分。每次从未排序部分取出第一个元素,把它插入到已排序部分的合适位置,使有序部分保持有序。重复这个过程,直到所有未排序元素都插入到有序部分中,整个数组就变为有序数组了。【核心思路】
代码分析与详解
对直接插入排序有了一个基本的了解以后,我们需要将这个思想去转化为代码
注意:
- 很多同学一开始刚有点思路就开始写代码,甚至什么都不想直接开始写,然后运行报错一大堆,各种数组访问越界、边界问题没有考虑、代码逻辑混乱。这其实是写程序的一个大忌,我们应该逐步分析,从简单到复杂、从单趟的排序到整体的排序去过渡
①写出单趟的的插入过程
- 首先对于单趟的逻辑,因为是要将一个数插入到一个有序区间中去,不妨设这个区间的最后一个位置为【end】,那这个待插入数据的位置就是【end + 1】,我们要将这个待插入的数据保存起来,因为在比较的过程中可能会造成数据的后移,那这个数就会被覆盖了
- 接着将待插入的数据与有序区间的最后一个数进行比较,因为默认选择排升序,所以是要将小的数据放到前面,所以若是这个待插入数据比 a[end] 要小,那 a[end] 便进行一个后移,但是呢,随着数据的不断增大,有序区也会越来越大,此时待插入数据就不只是和有序区的最后一个数据做比较了,需要和前面的所有数进行一个比较,那么我们就要将这段逻辑放在一个循环里。这个循环什么时候结束呢?就是当这个【end < 0】为止
- 若是在中途比较的过程中发现有比待插入数据还要小或者相等的数,就停止比较,跳出这个循环。因为随着有序区间中数的后移,end后一定会空出一个位置,此时呢执行a[end + 1] = tmp;就可以将这个待插入数据完整地放入有序区中并且使这个有序区依旧保持有序
根据上面的解析我们来用 图例 分析一下,单躺插入的过程,用例数组 a = [2,4,6,7,8,1] ,此时需要排一个升序。
- 根据数组我们可以发现有序区域为 【2,4,6,7,8】 ,需要插入的数据为 【1】
- 此时发现 8 大于 1 ,所以将 8 向后移动,之后 end 指针向前移动
此时发现 7 大于 1,所以将 7 向后移动,之后end 指针向前移动
- 此时发现 6 大于 1,所以将 6 向后移动,之后end 指针向前移动
- 此时发现 4 大于 1,所以将 4 向后移动,之后end 指针向前移动
此时发现 2 大于 1,所以将 2 向后移动,之后end 指针向前移动
- 此时发现 end < 0 跳出循环,temp = a[end + 1], 完成排序
代码展示
int end;
int tmp = a[end + 1]; //将end后的位置先行保存起来
while (end >= 0)
{if (tmp < a[end]){a[end + 1] = a[end]; //比待插值来得大的均往后移动end--; //end前移}else{break; //若是发现有相同的或者小于带插值的元素,则停下,跳出循环}
}
a[end + 1] = tmp; //将end + 1的位置放入保存的tmp值
②利用循环控制每一趟插入排序
这一块我们只需要将单趟的插入过程放在一个循环中即可,逐渐扩大这个有序区,因此【end】即为每次递增的变量i。
这里要注意的一点是❗循环的结束条件i < n - 1❗,这里不可以写成 i < n,若是写成这样那么【i】最后落的位置就是【n - 1】。此时end获取到这个位置后去保存tmp的值时就会造成造成数组的越界访问 a[n - 1 + 1] = a[n] ,那么这个位置就会出现一个随机值,所以大家在写这种循环的边界条件时一定提前做好分析✍,在运行之前保证心里胸有成竹
for (int i = 0; i < n - 1; ++i)
{int end = i;//单趟插入逻辑...
}
③完整的图例和代码演示
- 给定一组无序数组如下:
- 我们把首元素 6 作为有序区,此时有序区只有这一个元素:
- 第一轮,让元素 9 和有序区的元素依次比较,9 > 6,所以元素 9 和元素 6 无需交换。此时有序区的元素增加到两个:
- 第二轮,让元素 7 和有序区的元素依次比较,7 < 9,所以把元素 7 和元素 9 进行交换:
- 7 > 6,所以把元素 7 和元素 6 无需交换。此时有序区的元素增加到三个:
- 第三轮,让元素 4 和有序区的元素依次比较,4 < 9,所以把元素 4 和元素 9 进行交换:
- 4 < 7,所以把元素 4 和元素 7 进行交换:
- 4 < 6,所以把元素 4 和元素 6 进行交换:
- 此时有序区的元素增加到四个:
- 以此类推,插入排序一共会进行(数组长度-1)轮,每一轮的结果如下:
代码实现
#include <stdio.h>
#include <stdlib.h>// 插入排序
void InsertSort(int* a, int n)
{// [0,end]有序,把end+1位置的插入到前序// 控制 [0,end+1]有序for (int i = 0; i < n - 1; i++){int end = i;// 把 end 的后一个往前插入int temp = a[end + 1];while (end >= 0){// 升序if (temp < a[end]){// 向后挪动a[end + 1] = a[end];}else{break;}end--;}a[end + 1] = temp;}for (int i = 0; i < n; i++){printf("%d ", a[i]);}
}int main()
{int a[100];int sum;int j = 0;printf("请输入你要排序的数据 以-1结束:> \n");for (int i = 0; i < 100; i++){scanf("%d", &sum);if (sum == -1){break;}a[j++] = sum;}InsertSort(a, j);return 0;}
时间复杂度分析
【时间复杂度】:O(N2)
【空间复杂度】:O(1)
- 看了代码之后我们再来看看其复杂度,首先是对于时间复杂度,这块看的是这个排序算法执行的次数,那对于直接插入排序这一块来说,取决于数据数据的分步,设想若是一些随机的数字,然后每次取到的tmp值可能就需要插入到中间、前面、最后三种情况👇
- 【最好的】是每一趟的内部比较都不需要移动数据,只需要遍历一下这个数组即可,然后把待插入的数放在最后即可。那时间复杂度就是O(N);
- 【中间的】就是数据需要后移一般,那时间复杂度就是O(N/2)即O(N);
- 【最坏的】是需要插入到最前面的情况,因为对于tmp前面的每一个数字都需要向后挪动一位,也就意味着tmp要和这个数组的所有数字进行一个比较,然后又有N个数需要进行插入,此时时间复杂度就是O(N*2);
- 对于空间复杂度计算的额外的空间,在这里我们只是定义了一些变量,并没有去申额外的空间,因此空间复杂度为O(1)
以下就是对 直接插入排序 的学习
感谢您的三连支持!!!