插入排序:高效稳定的基础排序算法

📌 转载声明
原平台:CSDN
原作者:北域码匠
原文地址:点击跳转原文
备注:本文仅个人技术学习归档,无任何商用行为,全部版权归属原作者,原作者如需删稿可私信立刻下架。

前言

插入排序是一种简单高效的原地稳定排序 算法 ,与冒泡排序和选择排序并列为三大基础排序算法。其工作原理模拟了人工整理扑克牌的过程,实现逻辑直观易懂,代码简洁明了,无需复杂计算或递归结构。该算法最大的优势在于其自适应特性,当处理接近有序的数据时性能尤为出色。此外,它还是许多高级排序算法的重要优化基础。

核心原理

插入排序的核心思想是逐个选取元素并向前比对插入位置。与冒泡排序的相邻交换和选择排序的全域极值查找不同,插入排序避免了不必要的交换操作和全局遍历,仅需为当前元素找到准确的插入位置。算法每次仅处理一个无序元素,通过后移有序区间中较大的元素来腾出插入空间,从而最大限度地减少无效操作,在处理高有序度数据时表现尤为出色。

执行流程

初始化阶段

  • 将数组首元素作为初始有序区(长度为1的子数组)
  • 剩余元素构成无序区
  • 示例:数组 [5,2,4,6,1,3] 初始有序区为 [5]

元素选取

  • 从第二个元素开始顺序处理(索引1起始)
  • 将当前元素暂存为 key
  • 示例:首次迭代选取元素 2 作为 key

逆向比较

  • 从有序区末尾向前扫描比较
  • 使用指针 j 标记比较位置(初始值 i-1
  • 示例:首次比较时 j=0,对比元素 5key=2

元素后移

  • 当有序元素 > key 时:

    • 将该元素后移一位(A[j+1] = A[j]
    • j 指针前移继续比较

  • 示例:5 > 2,将 5 移至位置 1,数组变为 [5,5,4,6,1,3]

定位插入点

  • 持续比较直至:
    • 遇到 A[j] ≤ key 的元素
    • 或到达数组起始端(j=-1
  • 示例:j=-1 时终止比较

插入操作

  • key 值插入 j+1 位置
  • 示例:将 2 插入位置 0,数组更新为 [2,5,4,6,1,3]

迭代过程

  • 对每个无序元素重复上述步骤
  • 每次迭代后有序区长度 +1
  • 完整示例:
    • 第2次:处理 4[2,4,5,6,1,3]
    • 第3次:处理 6[2,4,5,6,1,3]
    • 第4次:处理 1[1,2,4,5,6,3]
    • 第5次:处理 3[1,2,3,4,5,6]

代码实现

以下是完整的 C# 插入排序实现代码,可对整数 数组 进行排序:

  1. public class InsertionSort
  2. {
  3. public static void Sort(int[] array)
  4. {
  5. if (array == null || array.Length <= 1)
  6. return;
  7. for (int i = 1; i < array.Length; i++)
  8. {
  9. int key = array[i];
  10. int j = i - 1;
  11. while (j >= 0 && array[j] > key)
  12. {
  13. array[j + 1] = array[j];
  14. j--;
  15. }
  16. array[j + 1] = key;
  17. }
  18. }
  19. }

使用示例

  1. int[] numbers = { 12, 11, 13, 5, 6 };
  2. InsertionSort.Sort(numbers);
  3. foreach (int num in numbers)
  4. {
  5. Console.Write(num + " ");
  6. }
  7. // 输出: 5 6 11 12 13

功能扩展

若需支持泛型类型,可采用以下 改进 方案:

  1. /**
  2. * 泛型版本的实现,支持任意类型的元素比较
  3. * @param <T> 可比较的元素类型,必须实现Comparable接口
  4. */
  5. public class GenericSorter<T extends Comparable<T>> {
  6. /**
  7. * 泛型冒泡排序实现
  8. * @param array 待排序的泛型数组
  9. */
  10. public void bubbleSort(T[] array) {
  11. int n = array.length;
  12. for (int i = 0; i < n-1; i++) {
  13. for (int j = 0; j < n-i-1; j++) {
  14. // 使用compareTo方法进行比较
  15. if (array[j].compareTo(array[j+1]) > 0) {
  16. // 交换元素
  17. T temp = array[j];
  18. array[j] = array[j+1];
  19. array[j+1] = temp;
  20. }
  21. }
  22. }
  23. }
  24. /**
  25. * 测试用例
  26. */
  27. public static void main(String[] args) {
  28. // 整数排序示例
  29. GenericSorter<Integer> intSorter = new GenericSorter<>();
  30. Integer[] intArray = {64, 34, 25, 12, 22, 11, 90};
  31. intSorter.bubbleSort(intArray);
  32. System.out.println("排序后的整数数组:");
  33. for (Integer num : intArray) {
  34. System.out.print(num + " ");
  35. }
  36. // 字符串排序示例
  37. GenericSorter<String> stringSorter = new GenericSorter<>();
  38. String[] strArray = {"banana", "apple", "pear", "orange"};
  39. stringSorter.bubbleSort(strArray);
  40. System.out.println("\n排序后的字符串数组:");
  41. for (String str : strArray) {
  42. System.out.print(str + " ");
  43. }
  44. }
  45. }

关键改进说明:

  1. 采用泛型类型参数<T extends Comparable<T>>确保类型安全性
  2. 通过compareTo()方法实现通用比较逻辑
  3. 兼容以下实现Comparable接口的类型:
    • 基本类型的包装类(如Integer、Double)
    • 字符串(String)
    • 自定义实现了Comparable接口的类

算法特性详解

时间复杂度分析

  • 最优情况(已排序数组):O(n)
    当输入数组已有序时,算法仅需一次遍历即可确认排序完成。
    示例:排序数组 [1, 2, 3, 4, 5] 仅需比较 n-1 次即可终止。

  • 最差情况(完全逆序):O(n²)
    数组完全逆序时,需进行 n(n-1)/2 次比较和交换。
    示例:排序数组 [5, 4, 3, 2, 1] 需要 10 次比较和交换操作。

  • 平均情况:O(n²)
    随机排列数组平均需要约 n²/4 次比较和交换。

空间复杂度

  • O(1) 原地排序
    仅需常数级额外空间存储交换元素,无需额外数据结构,直接在原数组操作。

稳定性

  • 稳定排序算法
    遇到相等元素时不改变其相对顺序。
    示例:排序 [(3,"a"), (3,"b"), (1,"c")] 后,两个 3 对应的记录仍保持 "a" 在前,"b" 在后。

其他特性

  • 适应性
    算法性能随输入数据的有序程度提升,对接近有序数据可能优于平均时间复杂度。

  • 交换次数
    平均交换次数约为比较次数的 1/3,最坏情况下交换次数与比较次数相当。

  • 实现复杂度
    实现简单直观,通常仅需两层循环,基础版本代码量一般在 10 行以内。

优缺点分析

优点

  1. 实现简单直观 采用类似整理扑克牌的思路,通过逐个元素插入已排序部分的正确位置来完成排序。这种直观的逻辑使其成为理想的入门算法,通常仅需5-10行嵌套循环即可实现,非常适合教学和快速开发场景。

  2. 有序数据高效处理 对接近有序的数据表现优异,时间复杂度可降至O(n)。例如处理已排序数组仅需n-1次比较,效率显著优于其他O(n²)算法,特别适合实时数据追加场景,如日志时间戳排序。

  3. 空间效率高且稳定 仅需O(1)额外空间,通过临时变量实现元素插入,避免了递归或辅助数组的开销。同时能保持相等元素的原始相对顺序,适用于需要多次排序的场景,如先按学号后按成绩排序。

缺点

  1. 大数据量性能受限 最坏情况下时间复杂度为O(n²)。例如处理10万个随机数时,操作次数可能高达50亿次,远不如O(n log n)算法。因此仅推荐用于小规模数据(n<100)的排序场景。

  2. 批量处理效率低 每次插入可能导致大量元素移位,如将最小值插入已排序数组末尾需要移动所有元素。这种串行特性难以利用CPU并行计算优势,也不适合处理非连续存储结构(如磁盘数据),仅对链表结构较为友好。

适用场景

小规模数据集排序

插入排序的时间复杂度为O(n²),对于数据量较小(例如n < 1000)的集合,其实际运行效率可能优于快速排序或归并排序等更复杂的算法,因为其常数因子较小且实现简单。例如,在 嵌入式系统 或移动设备上处理少量数据时,插入排序是理想选择。

基本有序的数据集

如果输入数据已经接近有序(例如90%的元素已处于正确位置),插入排序的性能接近O(n)。每次插入操作只需少量比较和移动,这使得它在处理 日志文件 、时间序列数据等部分有序的场景中表现优异。

需要稳定排序的场合

插入排序是稳定排序算法(即相等元素的相对顺序保持不变),适用于需要保留原始顺序的场景。例如:

  1. 对学生成绩表先按姓名排序后,再按分数排序时,同分学生的姓名仍需保持字母顺序

  2. 电商平台对商品先按上架时间排序后,再按价格排序时,同价位商品需保持时间先后

在线算法场景(数据逐步到达时实时排序)

由于插入排序可以"即到即排"的特性,它非常适合流式数据处理:

  1. 实时监控系统持续接收传感器数据时逐条插入有序队列

  2. 网络数据包重组过程中对到达的TCP片段进行顺序整理

  3. 游戏引擎每帧渲染前对动态添加的UI元素按层级排序

算法总结

插入排序模拟了人工排序的思维方式,通过精准的移位插入取代无效的遍历交换。相较于冒泡排序和选择排序,它具有最优的O(n)时间复杂度,自适应能力更强,同时兼具稳定性和低内存消耗的优势。尽管不适用于海量数据排序,但其简洁高效的特点使其成为基础排序算法中最实用的选择之一,也是编程学习和算法优化的关键基础

posted @ 2026-06-03 17:29  算法栈  阅读(11)  评论(0)    收藏  举报
📌 C#算法研习社|持续更新手写算法源码
已更新:MD2加密 | 冒泡/插入排序 | 原生C#图像处理,全部无第三方库手写实现
👍 收藏本博客,不错过每周算法更新 💡 关注主页,查看完整源码案例
关键词:C#算法 | 冒泡排序 | 插入排序 | MD2哈希 | C#原生编程 | 数据结构实战
©2026 C#算法研习社 | 原创技术随笔,转载请注明出处 | 专注零基础算法入门实战
定期更新:排序算法、哈希加密、图像底层处理实战教程