杰瑞科技汇

Python插入排序如何实现?

“插入排序”的英文名是 Insertion Sort,它是一种简单直观的排序算法。

Python插入排序如何实现?-图1
(图片来源网络,侵删)

算法核心思想

插入排序的核心思想就像我们整理一副扑克牌:

  1. 假设有序:我们首先认为手中的第一张牌已经是有序的。
  2. 逐个插入:我们从第二张牌开始,依次取出每一张牌。
  3. 比较和移动:将取出的这张牌,与它前面已经排好序的牌进行比较,从后向前找到它应该在的位置,并将这个位置以及之后的所有牌都向后移动一位,为这张新牌腾出空间。
  4. 插入:将这张牌插入到正确的位置。

这个过程重复进行,直到所有的牌都被插入到正确的位置,整个序列就变得有序了。


算法步骤分解

假设我们要对一个数组 [5, 2, 4, 6, 1, 3] 进行从小到大排序。

  1. 初始状态[5, 2, 4, 6, 1, 3]

    Python插入排序如何实现?-图2
    (图片来源网络,侵删)
    • 我们把第一个元素 5 看作是一个已排序的子数组 [5]
    • 剩下的 [2, 4, 6, 1, 3]未排序的部分
  2. 第一轮:插入 2

    • 从未排序部分取出第一个元素 2
    • 2 与已排序子数组的最后一个元素 5 比较,因为 2 < 55 需要向后移动一位。
    • 已排序子数组变为 [ , 5]2 被放在空位上。
    • 结果: [2, 5, 4, 6, 1, 3]
  3. 第二轮:插入 4

    • 从未排序部分取出 4
    • 4 与已排序子数组的最后一个元素 5 比较,因为 4 < 55 向后移动。
    • 已排序子数组变为 [2, , 5]
    • 再将 4 与新的最后一个元素 2 比较,因为 4 > 2,所以找到了正确的位置。
    • 结果: [2, 4, 5, 6, 1, 3]
  4. 第三轮:插入 6

    • 从未排序部分取出 6
    • 6 与已排序子数组的最后一个元素 5 比较,因为 6 > 56 不需要移动,直接放在 5 的后面。
    • 结果: [2, 4, 5, 6, 1, 3]
  5. 第四轮:插入 1

    • 从未排序部分取出 1
    • 1 需要与 6, 5, 4, 2 依次比较,并把这些元素都向后移动,直到找到最前面。
    • 结果: [1, 2, 4, 5, 6, 3]
  6. 第五轮:插入 3

    • 从未排序部分取出 3
    • 3 需要与 6, 5, 4 依次比较并移动,直到找到 24 之间。
    • 结果: [1, 2, 3, 4, 5, 6]
  7. 结束:未排序部分为空,排序完成。


Python 代码实现

下面是插入排序的 Python 代码实现,包含了详细的注释来解释每一步。

标准实现(推荐)

def insertion_sort(arr):
    """
    对给定的数组进行插入排序
    :param arr: 需要排序的列表
    :return: 无返回值,直接在原列表上进行排序
    """
    # 遍历数组从第二个元素开始 (索引 1)
    # 因为第一个元素可以看作是一个只有一个元素的已排序子数组
    for i in range(1, len(arr)):
        # key 是当前待插入的元素
        key = arr[i]
        # j 是已排序子数组的最后一个元素的索引
        j = i - 1
        # 将 key 与已排序子数组中的元素从后向前进行比较
        # key 比前一个元素小,就将前一个元素向后移动
        # 循环继续,直到找到 key 的正确位置或到达数组开头
        while j >= 0 and key < arr[j]:
            arr[j + 1] = arr[j]  # 将元素向后移动一位
            j -= 1  # 指向前一个元素继续比较
        # 循环结束后,j + 1 key 的正确位置
        arr[j + 1] = key
# --- 使用示例 ---
my_list = [12, 11, 13, 5, 6]
print("原始数组:", my_list)
insertion_sort(my_list)
print("排序后数组:", my_list)

输出:

原始数组: [12, 11, 13, 5, 6]
排序后数组: [5, 6, 11, 12, 13]

更直观的写法(使用 popinsert

这种写法更容易理解,但性能稍差,因为 popinsert 操作本身就有时间开销。

def insertion_sort_visual(arr):
    """
    使用 pop 和 insert 实现的插入排序,更直观但性能略低
    """
    # 遍历数组从第二个元素开始
    for i in range(1, len(arr)):
        # 取出当前待插入的元素
        current_value = arr.pop(i)
        # 从已排序部分的末尾开始向前查找插入位置
        j = i - 1
        while j >= 0 and current_value < arr[j]:
            j -= 1
        # 在找到的位置插入元素
        arr.insert(j + 1, current_value)
# --- 使用示例 ---
my_list_2 = [12, 11, 13, 5, 6]
print("原始数组:", my_list_2)
insertion_sort_visual(my_list_2)
print("排序后数组:", my_list_2)

算法分析

时间复杂度

  • 最佳情况O(n),当输入数组已经是排序好的状态时,对于每个元素 arr[i]while 循环只会比较一次(arr[i-1] <= arr[i]),然后就结束了,总共需要 n-1 次比较。
  • 最坏情况O(n²),当输入数组是逆序排序时,对于每个元素 arr[i]while 循环需要比较 i 次,总比较次数为 1 + 2 + 3 + ... + (n-1) = n(n-1)/2。
  • 平均情况O(n²),对于随机顺序的数组,平均时间复杂度也是 O(n²)。

空间复杂度

  • O(1),插入排序是原地排序算法,它不需要额外的存储空间来存储另一份数组,只使用了常数个额外变量(如 key, j 等)。

稳定性

  • 稳定,插入排序是稳定的排序算法,当两个相等元素的值相等时,由于我们用的是 key < arr[j](而不是 <=),所以不会发生交换,它们原有的相对顺序会保持不变。

优缺点总结

优点

  1. 实现简单:代码逻辑非常直观,容易理解和实现。
  2. 空间效率高:是原地排序,空间复杂度为 O(1)。
  3. 对小规模数据高效:当数据量 n 很小时,插入排序的性能可能比一些更复杂的算法(如快速排序)要好。
  4. 稳定排序:可以保持相等元素的原始顺序。
  5. 自适应:对于部分有序的数据,效率很高。

缺点

  1. 性能较差:对于大规模数据,时间复杂度为 O(n²),效率非常低,远不如 O(n log n) 的算法(如归并排序、堆排序、快速排序)。
  2. 数据移动多:在插入元素时,需要移动大量的元素,这在链表结构中可以通过指针操作优化,但在数组中成本较高。
特性 描述
中文名 插入排序
英文名 Insertion Sort
核心思想 将数组分为已排序和未排序两部分,逐个将未排序元素插入到已排序部分的正确位置。
时间复杂度 最佳: O(n), 平均: O(n²), 最差: O(n²)
空间复杂度 O(1)
稳定性 稳定
适用场景 数据量小、数据基本有序、或作为更复杂排序算法的子过程(如 Timsort)。

在 Python 的实际开发中,你几乎不会自己手动实现插入排序,因为内置的 list.sort() 方法和 sorted() 函数使用了非常高效的 Timsort 算法,它结合了归并排序和插入排序的优点,在各种情况下都能表现出色,学习插入排序是理解排序算法基础和思想的重要一步。

分享:
扫描分享到社交APP
上一篇
下一篇