Python数据结构(十二):插入排序详解
本文是Python数据结构系列的第十二篇,我们将深入探讨基本排序算法中的插入排序。插入排序是一种简单直观的排序算法,通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
一、插入排序的基本概念
插入排序(Insertion Sort)是一种简单的排序算法。它的工作原理:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。因此需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
插入排序的名称由来
这个算法的名字直接反映了它的工作原理:"插入"未排序元素到已排序序列的正确位置。算法的核心思想就是通过"插入"操作来逐步构建有序序列。
插入排序的基本思想
插入排序的基本思想可以概括为:将数组分为已排序和未排序两部分,每次从未排序部分取出一个元素,将其插入到已排序部分的正确位置,直到所有元素都插入完毕。
插入排序的直观理解:
想象一下整理手中的扑克牌:我们从左到右依次将每张牌插入到已整理牌堆中的正确位置,保持手中的牌始终有序。
二、插入排序的算法原理
2.1 算法步骤
插入排序的算法步骤可以详细描述如下:
- 初始状态:将第一个元素视为已排序序列
- 取出元素:取出下一个未排序元素作为待插入元素
- 扫描比较:从已排序序列的末尾开始向前扫描
- 移动元素:如果已排序元素大于待插入元素,将该元素向后移动一位
- 插入元素:找到合适位置后,将待插入元素插入
- 重复过程:重复步骤2-5,直到所有元素都插入完毕
2.2 排序过程演示
以下是一个具体的排序过程示例:
初始数组: [64, 34, 25, 12, 22, 11, 55]

第一轮插入:
- 将第一个元素64视为已排序:[64]
- 取出第二个元素34,与64比较,34<64,将64后移,插入34
- 结果: [34, 64, 25, 12, 22, 11, 55]
第二轮插入:
- 已排序部分:[34, 64]
- 取出第三个元素25,从后向前比较:25<64,64后移;25<34,34后移;插入25
- 结果: [25, 34, 64, 12, 22, 11, 55]
第三轮插入:
- 已排序部分:[25, 34, 64]
- 取出第四个元素12,从后向前比较并移动元素,插入12
- 结果: [12, 25, 34, 64, 22, 11, 55]
继续这个过程...
最终结果: [11, 12, 22, 25, 34, 55, 64]
三、Python实现插入排序
3.1 基础实现
python
def insertion_sort(arr):
"""
插入排序基础实现
:param arr: 待排序的列表
:return: 排序后的列表
"""
# 从第二个元素开始,第一个元素视为已排序
for i in range(1, len(arr)):
# 当前待插入的元素
key = arr[i]
# 已排序部分的最后一个元素索引
j = i - 1
# 从后向前扫描,找到插入位置
while j >= 0 and key < arr[j]:
# 将大于key的元素向后移动
arr[j + 1] = arr[j]
j -= 1
# 插入key到正确位置
arr[j + 1] = key
return arr
# 测试插入排序
arr = [64, 34, 25, 12, 22, 11, 55]
print(f'原始数组: {arr}')
print(f'插入排序后: {insertion_sort(arr.copy())}')
3.2 实现细节
外层循环
python
for i in range(1, len(arr)):
- 从第二个元素开始遍历(索引1)
- i表示当前待插入元素的索引
- 每次循环处理一个未排序元素
待插入元素
python
key = arr[i]
- 保存当前待插入的元素值
- 后续移动操作会覆盖该位置,需要先保存
内层循环
python
j = i - 1
while j >= 0 and key < arr[j]:
- j从已排序部分的最后一个元素开始
- 向前扫描,直到找到插入位置或到达开头
- 条件:j有效且当前元素大于key
元素移动
python
arr[j + 1] = arr[j]
j -= 1
- 将大于key的元素向后移动一位
- j向前移动,继续比较前一个元素
插入操作
python
arr[j + 1] = key
- 找到插入位置后,将key放入
- j+1是因为while循环结束时j指向小于key的元素或-1
四、插入排序的时间复杂度分析
4.1 时间复杂度计算
最好情况(数组已排序)
- 每次只需比较一次,不需要移动
- 比较次数:n-1次
- 移动次数:0次
- 时间复杂度:O(n)
最坏情况(数组完全逆序)
- 每次需要比较和移动所有已排序元素
- 比较次数:1 + 2 + ... + (n-1) = n(n-1)/2
- 移动次数:n(n-1)/2
- 时间复杂度:O(n²)
平均情况
- 大约需要n²/4次比较和移动
- 时间复杂度:O(n²)
4.2 空间复杂度
- 插入排序是原地排序算法
- 只需要常数级别的额外空间
- 空间复杂度:O(1)
4.3 时间复杂度总结
| 情况 | 比较次数 | 移动次数 | 时间复杂度 |
|---|---|---|---|
| 最好 | n-1 | 0 | O(n) |
| 最坏 | n(n-1)/2 | n(n-1)/2 | O(n²) |
| 平均 | ≈n²/4 | ≈n²/4 | O(n²) |
五、插入排序的特点
5.1 优点
- 简单易懂:算法思想直观,容易理解和实现
- 原地排序:只需要常数级别的额外内存空间
- 稳定排序:相等元素的相对位置不会改变
- 适应性好:对部分有序数据效率很高
- 在线算法:可以边接收数据边排序
5.2 缺点
- 效率低下:平均时间复杂度为O(n²),不适合大规模数据
- 移动频繁:需要大量元素移动操作
- 不适合链表:随机访问特性在链表上效率低
5.3 适用场景
- 小规模数据:数据量很小(n < 100)时效率高
- 部分有序数据:数据基本有序时性能接近O(n)
- 稳定排序需求:需要保持相等元素相对顺序
- 在线排序:数据逐个到达时需要实时排序
六、插入排序与其他排序算法的比较
6.1 与冒泡排序比较
| 特性 | 插入排序 | 冒泡排序 |
|---|---|---|
| 比较次数 | 最好n-1,最坏n(n-1)/2 | 最好n-1,最坏n(n-1)/2 |
| 交换/移动次数 | 最好0,最坏n(n-1)/2 | 最好0,最坏n(n-1)/2 |
| 稳定性 | 稳定 | 稳定 |
| 适应性 | 对部分有序数据适应性很强 | 有一定适应性 |
| 实际性能 | 通常比冒泡排序快 | 通常较慢 |
6.2 与选择排序比较
| 特性 | 插入排序 | 选择排序 |
|---|---|---|
| 比较次数 | 最好n-1,最坏n(n-1)/2 | 固定n(n-1)/2 |
| 移动次数 | 最好0,最坏n(n-1)/2 | 最多n-1次交换 |
| 稳定性 | 稳定 | 不稳定 |
| 适应性 | 对部分有序数据适应性很强 | 无适应性 |
| 实际性能 | 通常比选择排序快 | 通常较慢 |
总结
插入排序是最基础、最直观的排序算法之一,虽然在大规模数据上效率不高,但在特定场景下仍有其价值。
核心要点回顾:
- 基本思想:构建有序序列,逐个插入未排序元素
- 算法复杂度:最好O(n),最坏O(n²),平均O(n²)
- 稳定性:插入排序是稳定排序算法
- 适应性:对部分有序数据效率很高
- 原地排序:空间复杂度O(1)
算法特点总结:
- 简单性:算法思想简单,易于理解和实现
- 稳定性:保持相等元素的相对顺序
- 适应性:能利用输入数据的已有顺序
- 在线性:可以处理动态到达的数据
学习意义:
插入排序不仅是排序算法的基础,也体现了许多高级算法的核心思想。理解插入排序对于学习更复杂的算法(如希尔排序、归并排序)有重要意义。
在实际编程中,虽然对于大规模数据会使用更高效的排序算法,但在小规模数据、部分有序数据或需要稳定排序的场景下,插入排序仍然是合理的选择。
在下一篇中,我们将探讨高级排序算法,包括希尔排序、快速排序和归并排序,这些算法在效率上比基本排序算法有显著提升,适合处理大规模数据。
注意:本文是Python数据结构系列的第十二篇,重点讲解插入排序的基本概念和实现。在实际编程中,应根据具体需求选择合适的排序算法,理解各种算法的特点和适用场景是成为优秀程序员的重要基础。