【数据结构初阶】--从排序算法原理分析到代码实现操作,参透插入排序的奥秘!

【数据结构初阶】--从排序算法原理分析到代码实现操作,参透插入排序的奥秘!


在这里插入图片描述


🔥@晨非辰Tong: 个人主页
👀专栏:《C语言》《数据结构与算法入门指南》
💪学习阶段:C语言、数据结构与算法初学者
⏳“人理解迭代,神理解递归。”


文章目录


引言

在排序算法的世界里,插入排序家族看似简单,却暗藏效率之争。当朴素的直接插入排序,遇上剑走偏锋的希尔排序,==谁才是真正的性能王者?==本文将为您揭晓答案。

获取源码》点我!!《


一、排序的概念与运用

1.1 什么是排序?

排序:是将一组数据按照特定顺序重新排列的过程,通常是升序或降序排列。

1.2 运用场景有哪些?

排序在数据处理和展示中有着非常广泛的应用。比如下面这几个典型的场景:

1.2.1 学生成绩排名

在这里插入图片描述

1.2.2 视频平台热度排行榜

在这里插入图片描述

1.3 常见的排序算法

在这里插入图片描述

–下面先介绍插入排序的两种排序方法。


二、直接插入排序

2.1 基本思想

直接插入排序:是一种简单直观的排序算法,工作原理类似于我们整理扑克牌的方式:将未排序的元素按其关键码值的大小逐个插入到已排序序列的适当位置,直到所有的记录插入完为止,最后得到一个新的有序序列。

在这里插入图片描述

2.1.1 算法思路

首先,在一个乱序数组中,假设将变量 end 指向第一个元素当作有序(因为只有它自己,肯定是有序的),变量 temp 指向并保存未排序元素的第一位,基本方法就是将 endtemp 指向的元素进行比较。

简单的看第一次交换,从下标1开始,如果 arr[end] < temp ,那么二者就不用交换。end 一直比较到下标2的位置,这时 arr[end] > temptemp 先将1保存,随后 end-- 开始向前与temp比较寻找arr[end] < temp

end--过程中,若仍是 arr[end] > temp ,就 arr[end + 1] = arr[end] 。直到end-- < 0 结束比较(循环条件), arr[end + 1] = temp 。当然,如果过程中 arr[end] < temp 就结束比较, arr[end + 1] = temp

2.1.2 特性总结

  • 元素集合越接近有序,直接插入排序算法时间效率越高;
  • 时间复杂度:O(N2);
  • 空间复杂度:O(N);
在这里插入图片描述

2.2 排序源码呈现

//Sort.c文件//直接插入排序voidInsertSort(int* arr,int n){//外层循环:遍历整个数组(一直在尾部)for(int i =0; i < n -1; i++){//变量追踪有序尾部int end = i;//变量存储第一个无序元素int temp = arr[end +1];//内层循环:将有序与无序对比//升序排列;> 降序排列:< while(end >=0){//比较end、temp//end大if(arr[end]> temp){ arr[end +1]= arr[end]; end--;}else{break;}} arr[end +1]= temp;}}//打印voidPrintArr(int* arr,int n){for(int i =0; i < n; i++){printf("%d ", arr[i]);}printf("\n");}//————————————————————————————————————————————————////test.c文件test01(){int arr[]={5,3,9,6,2,4};int n =sizeof(arr)/sizeof(arr[0]);PrintArr(arr, n);InsertSort(arr, n);PrintArr(arr, n);}intmain(){test01();return0;}
在这里插入图片描述

2.2.1注意要点

- for (int i = 0; i < n - 1; i++)循环条件设置:
如图所示:外层循环主要是进行end的行为,因为end是追踪无序数列的尾部,当end到达下标为:n-1位置时,数列已经变成了有序,无需再比较。所以是i < n - 1.

在这里插入图片描述

三、希尔排序

3.1 基本思想

希尔排序:也称为缩小增量排序,是插入排序的一种高效的改进版本–>将整个待排序的序列分割成若干个子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序

3.1.1 算法思路

  • 初始化间隔:先选定⼀个初始间隔值 gap (通常n / 3 + 1);
  • 分组排序:把待排序所有记录按照 gap 进行分组,所有相距为 gap 的元素被划分到同一组,并对每⼀组内进行直接插入排序;
  • 缩小间隔:然后 gap = gap / 3 + 1 得到下⼀个间隔,重新分组再进行插入排序,当 gap=1 时,就相当于直接插入排序。

3.1.2 特性总结

  • 希尔排序是对直接插入排序的优化。
  • 当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。

时间复杂度:不定,一般为O(N1.3)

在这里插入图片描述

3.2 排序源码呈现

//Sort.c文件//2)希尔排序voidShellSort(int* arr,int n){int gap = n;while(gap >1){//取间隔 gap = gap /3+1;//分组循环 for(int i =0; i < n - gap; i++)//i++比i+=gap少了一个循环{int end = i;//相同间隔的元素int tmp = arr[end + gap];//循环比较while(end >=0){if(arr[end]> tmp){ arr[end + gap]= arr[end];//不再是end--,为了同组比较内替换 end -= gap;}else{break;}} arr[end + gap]= tmp;}}}//————————————————————————————————————————————————////test.c文件test01(){int arr[]={5,3,9,6,2,4};int n =sizeof(arr)/sizeof(arr[0]);printf("排序之前:");PrintArr(arr, n);//希尔排序ShellSort(arr, n);printf("排序之后:");PrintArr(arr, n);}intmain(){test01();return0;}

3.2.1注意要点

  • 为什么gap = n / 3 + 1?
    :为了确保最后一次进行整体的直接插入排序,gap最后必须为1
  • 是分组插入,不是分别排序
    :希尔排序是交替处理各个分组(i++),而不是完整排序一个分组后再处理下一个。
  • 最后一轮必须是gap=1
    :无论前面使用什么间隔序列,最后一遍必须是 gap=1 的标准插入排序

四、谁才是插入排序家族的"效率之王"

下面将从几个维度来比较直接插入排序、希尔排序,以此得出谁才是插入排序的效率之王!

对比维度直接插入排序希尔排序(缩小增量排序)
核心思想将待排序元素逐个插入到已排序序列的合适位置,类似整理扑克牌的过程。按增量将序列分割为多个子序列,对每个子序列进行直接插入排序,逐步缩小增量至 1(最终完成整体排序)。
时间复杂度最佳:O (n),平均:O (n²),最坏:O (n²)(序列逆序)最佳:O (n log n),平均情况:O (n¹.³)(与增量选择相关), 最坏:O (n²)(依赖增量序列)
空间复杂度稳定(相等元素的相对位置不变)不稳定(子序列排序可能破坏相等元素的相对位置)
优势实现简单,对基本有序数据效率高突破直接插入排序的 O (n²) 瓶颈,对无序数据的排序效率显著更高
劣势对大规模无序数据效率极低(需频繁移动元素)实现较复杂(需设计增量序列),增量选择对性能影响大,稳定性差

经综合对比考量:希尔排序凭借其 O(n^1.3) 的平均时间复杂度,在插入排序家族中稳坐"效率之王"的宝座,特别是在处理无序的中等规模数据时优势明显。

但直接插入排序在特定场景下(极小数据、基本有序、要求稳定)仍有其不可替代的价值,可视为"特定场景专家"。


总结

🍓 我是晨非辰Tong!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 

结语:
算法世界犹如浩瀚星河,每一次对基础知识的深入探索,都像是点亮一颗新的星辰。直接插入排序与希尔排序的这场“兄弟之争”,让我们看到算法优化并非遥不可及——有时,一个巧妙的思路转变,就能带来性能的飞跃。

希尔排序虽以 “效率之王” 加冕,但直接插入排序在特定场景下的价值依然不可替代。这正印证了一个编程哲学:没有最好的算法,只有最合适的算法。掌握它们的本质差异,才能在未来的开发实践中做出最明智的选择。

希望这篇分析能让你对插入排序家族有了更立体的认识。别忘了在评论区留下你的看法:
➤ 实战中,你会更倾向于使用哪种算法?为什么?
➤ 你还想了解哪些排序算法的对比分析?

【下期预告】:当"直接选择"对阵"堆排序",谁才是选择排序的终极赢家?我们下回分晓!

Read more

排序算法的速度美学:快速排序深度漫游

排序算法的速度美学:快速排序深度漫游

目录 一、快速排序思想 二、Hoare版本 1、Hoare版本介绍 2、编码实操 3、时间复杂度分析 4、有序情况优化 4.1 随机选keyi 4.2 三数取中 小贴士: 5、稳定性分析 三、挖坑法 1、挖坑法介绍 2、编码实操 四、lomuto前后指针版本 1、前后指针版本介绍 2、编码实操 3、小区间优化 五、迭代版本(非递归) 1、递归的缺陷 2、非递归思路 3、编码实操 六、三路划分 1、三路划分思想 2、

By Ne0inhk
算法王冠上的明珠——动态规划之路径问题(第一篇)

算法王冠上的明珠——动态规划之路径问题(第一篇)

目录 1. 什么叫路径类动态规划 一、核心定义(通俗理解) 二、核心特征(识别这类问题的关键) 2. 动态规划步骤 状态表示 状态转移方程 初始化 填表顺序 返回值 3. 例题讲解 3.1 LeetCode62. 不同路径 3.2 LeetCode63. 不同路径 II 3.3 LeetCodeLCR 166. 珠宝的最高价值 今天我们来聊一聊动态规划的路径类问题。 1. 什么叫路径类动态规划 路径类动态规划是 动态规划的一个重要分支,核心解决 “从起点到终点的路径相关问题”—— 比如 “路径总数”“最短路径长度”“路径上的最大 / 最小和” 等,其本质是通过 “状态递推” 避免重复计算,高效求解多阶段决策的路径问题。 一、

By Ne0inhk
链表专题(九):应用篇的无冕之王——「LRU 缓存机制」

链表专题(九):应用篇的无冕之王——「LRU 缓存机制」

场景想象: 你有一个书架(缓存),容量有限(比如只能放 3 本书)。 规则是 “最近最少使用 (Least Recently Used)” 淘汰: 1. 读取:如果你读了一本书,它就变得“新鲜”了,要把它抽出来放到最前面。 2. 插入: * 如果书架没满,新书直接插到最前面。 * 如果书架满了,必须把最后面那本(最久没人看的那本)扔掉,然后把新书插到最前面。 技术难点: 我们需要两个操作:get(查找)和 put(插入/更新)。 题目要求这两个操作的时间复杂度必须都是 $O(1)$。 * 只用数组? 插入/删除慢($O(N)$)。 * 只用链表? 查找慢($O(N)$)。 * 只用对象/

By Ne0inhk
c++基础树上问题——求lca(最近公共祖先)的几种方法,算法必看哟!

c++基础树上问题——求lca(最近公共祖先)的几种方法,算法必看哟!

目录  一,lca简介 核心定义 应用场景 关键特性 二,求lca的方法 方法一:朴素求lca(倍增求lca) 代码: 例题: 问题描述 输入格式 输出格式 样例输入 样例输出 样例说明 测评数据规模 代码详解: 方法二:tarjan求lca 1. 时间复杂度极低,适合大规模查询 2. 利用并查集实现 “路径压缩”,合并与查询高效 3. 一次遍历处理所有查询,减少树的访问次数 4. 适用于静态树的批量查询场景 5. 实现逻辑直观,依托 DFS 与并查集的天然契合 总结 代码: 例题: 注:本文题目均来自蓝桥杯官网公开题目,仅用于技术讨论和算法学习。  一,lca简介         LCA(least

By Ne0inhk