插入排序

算法定义

插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理类似于我们手中整理扑克牌的过程:将一张牌插入到已经排好序的牌中的正确位置。插入排序通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序在实现上,通常采用原地排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

算法思想

插入排序的基本思想:

  1. 分割数组:将数组分为已排序部分和未排序部分
  2. 逐个插入:从未排序部分取出一个元素,在已排序部分找到合适位置插入
  3. 移动元素:为了给新元素腾出位置,需要将已排序部分的某些元素向后移动
  4. 重复过程:重复上述过程,直到所有元素都被插入到正确位置

核心特点

  • 增量构建:逐步构建有序序列
  • 局部有序:始终保持已处理部分的有序性
  • 自适应性:对于部分有序的数据表现较好

运算过程

以升序排列为例,插入排序的详细步骤:

  1. 初始状态:将第一个元素视为已排序部分
  2. 第一轮:取第二个元素,与已排序部分比较并插入正确位置
  3. 第二轮:取第三个元素,在已排序部分中找到正确位置插入
  4. 重复:继续处理剩余元素,直到全部排序完成

图解示例

假设有数组 [5, 2, 4, 6, 1, 3]:

初始状态: [5, 2, 4, 6, 1, 3] ↑ ↑ 已排序 未排序 第1轮: 插入元素2 比较: 2 < 5,将5向右移动,2插入到位置0 [2, 5, 4, 6, 1, 3] ↑ ↑ ↑ 已排序 未排序 第2轮: 插入元素4 比较: 4 < 5但4 > 2,将5向右移动,4插入到位置1 [2, 4, 5, 6, 1, 3] ↑ ↑ ↑ ↑ 已排序 未排序 第3轮: 插入元素6 比较: 6 > 5,直接放在位置3(无需移动) [2, 4, 5, 6, 1, 3] ↑ ↑ ↑ ↑ ↑ 已排序 未排序 第4轮: 插入元素1 比较: 1比所有已排序元素都小,将所有元素右移,1插入到位置0 [1, 2, 4, 5, 6, 3] ↑ ↑ ↑ ↑ ↑ ↑ 已排序 未排序 第5轮: 插入元素3 比较: 3 > 2但3 < 4,将4,5,6向右移动,3插入到位置2 [1, 2, 3, 4, 5, 6] ↑ ↑ ↑ ↑ ↑ ↑ 全部已排序

示例Python程序

def insertion_sort(arr):
    """
    插入排序函数
    参数:
        arr: 待排序的列表
    返回:
        排序后的列表
    """
    # 从第二个元素开始(索引1),因为第一个元素可以看作已排序
    for i in range(1, len(arr)):
        key = arr[i]  # 当前要插入的元素
        j = i - 1     # 已排序部分的最后一个元素的索引
        
        # 在已排序部分中从后向前寻找插入位置
        # 同时将大于key的元素向后移动
        while j >= 0 and arr[j] > key:
            arr[j + 1] = arr[j]  # 将元素向后移动
            j -= 1
        
        # 将key插入到正确位置
        arr[j + 1] = key
        
        # 可选:打印每轮排序后的结果
        print(f"第{i}轮插入后: {arr}")
    
    return arr

def insertion_sort_demo():
    """插入排序演示"""
    data = [5, 2, 4, 6, 1, 3]
    print(f"原始数组: {data}")
    print("排序过程:")
    
    sorted_data = insertion_sort(data.copy())
    print(f"最终结果: {sorted_data}")

# 优化版本:二分查找插入位置
def binary_insertion_sort(arr):
    """
    使用二分查找优化的插入排序
    减少比较次数,但移动次数不变
    """
    for i in range(1, len(arr)):
        key = arr[i]
        # 使用二分查找找到插入位置
        left, right = 0, i
        while left < right:
            mid = (left + right) // 2
            if arr[mid] > key:
                right = mid
            else:
                left = mid + 1
        
        # 移动元素为key腾出空间
        for j in range(i, left, -1):
            arr[j] = arr[j - 1]
        
        # 插入key
        arr[left] = key
    
    return arr

# 运行演示
insertion_sort_demo()

程序解释

基本版本

  1. 外层循环for i in range(1, len(arr)) 从第二个元素开始处理
  2. 保存当前元素key = arr[i] 保存要插入的元素
  3. 初始化指针j = i - 1 指向已排序部分的最后一个元素
  4. 寻找插入位置while j >= 0 and arr[j] > key 从后向前比较
  5. 移动元素arr[j + 1] = arr[j] 将大于key的元素后移
  6. 插入元素arr[j + 1] = key 将key插入到正确位置

优化版本

  • 使用二分查找确定插入位置,减少比较次数
  • 但元素移动次数不变,整体时间复杂度仍为O(n²)

算法分析

时间复杂度

  • 最好情况:O(n) - 数组已经有序,每个元素只需要比较一次
  • 最坏情况:O(n²) - 数组完全逆序,每个元素都需要与前面所有元素比较
  • 平均情况:O(n²) - 随机数据的期望比较次数

详细分析

  • 第i轮最多需要i次比较和i次移动
  • 总比较次数:1 + 2 + ... + (n-1) = n(n-1)/2 = O(n²)

空间复杂度

  • O(1):插入排序是原地排序算法,只需要常数级别的额外空间

稳定性

  • 稳定:相等元素的相对位置不会改变

优缺点分析

优点

  1. 简单实现:算法思路清晰,代码简洁
  2. 原地排序:不需要额外的存储空间
  3. 稳定排序:不会改变相等元素的相对位置
  4. 自适应性:对于部分有序的数据效率较高
  5. 在线算法:可以在接收数据的同时进行排序
  6. 小数据高效:对于小规模数据集表现良好

缺点

  1. 大数据效率低:O(n²)的时间复杂度在大数据集上表现不佳
  2. 移动开销大:需要频繁移动元素

与其他排序算法的比较

特性 插入排序 选择排序 冒泡排序
时间复杂度(最好) O(n) O(n²) O(n)
时间复杂度(最坏) O(n²) O(n²) O(n²)
时间复杂度(平均) O(n²) O(n²) O(n²)
空间复杂度 O(1) O(1) O(1)
稳定性 稳定 不稳定 稳定
自适应性
在线性

应用场景

插入排序特别适用于:

  1. 小规模数据:当数据量较小(通常n < 50)时效率较高
  2. 部分有序数据:当数据基本有序时,插入排序表现优异
  3. 在线排序:数据流场景,边接收数据边排序
  4. 混合排序算法:作为快速排序等高级算法的子程序
  5. 教学演示:帮助理解排序的基本概念

实际应用示例

# 示例:对学生成绩进行排序
def sort_student_scores():
    students = [
        ("Alice", 85),
        ("Bob", 92),
        ("Charlie", 78),
        ("Diana", 96),
        ("Eve", 88)
    ]
    
    # 按成绩排序(使用插入排序思想)
    for i in range(1, len(students)):
        key_student = students[i]
        j = i - 1
        
        while j >= 0 and students[j][1] > key_student[1]:
            students[j + 1] = students[j]
            j -= 1
        
        students[j + 1] = key_student
    
    return students

# 运行示例
sorted_students = sort_student_scores()
for name, score in sorted_students:
    print(f"{name}: {score}")

练习题

  1. 基础练习:实现插入排序的降序版本
  2. 优化练习:实现二分插入排序并比较性能
  3. 应用练习:使用插入排序对字符串数组按长度排序
  4. 分析练习:测试插入排序在不同数据分布下的性能表现

插入排序虽然在大数据集上效率不高,但其简单性、稳定性和对小数据集的高效性使其在实际应用中仍有重要价值,特别是作为其他复杂排序算法的组成部分。