排序算法python版(3)-插入排序算法:从扑克牌到代码的直观理解

发布时间:2026/7/16 22:58:26
排序算法python版(3)-插入排序算法:从扑克牌到代码的直观理解 1. 从扑克牌到代码插入排序的直观理解每次打扑克牌时我们都会不自觉地进行一种动态排序——将新摸到的牌插入到手中已排序的牌堆里。这个看似简单的动作恰恰揭示了插入排序Insertion Sort的核心思想。想象你左手拿着已经排好序的牌比如3、7、9右手摸到一张新牌5。你会怎么做从右向左扫描左手的牌9→7→3当遇到第一张比5小的牌3时停止将5插入到3的右侧这个过程用Python代码实现时只需要将左手牌换成数组比较牌大小换成元素比较def insertion_sort(arr): for i in range(1, len(arr)): # 第一张牌默认已排序从第二张开始 current arr[i] # 当前摸到的新牌 j i-1 # 从已排序部分的最后开始比较 while j 0 and current arr[j]: arr[j1] arr[j] # 将较大的牌向右移动 j - 1 arr[j1] current # 找到合适位置插入实测一个倒序数组的排序过程nums [5,2,4,6,1,3] insertion_sort(nums) print(nums) # 输出[1, 2, 3, 4, 5, 6]提示在算法可视化网站如visualgo.net上看插入排序的动画会更容易理解元素移动的过程2. 算法实现细节剖析2.1 核心逻辑分解让我们拆解代码中的关键步骤外层循环for i in range(1, len(arr))从第二个元素开始遍历索引1因为单个元素索引0默认已排序当前待插入值current arr[i]保存当前元素值后续移动元素时会覆盖这个位置内层循环while j 0 and current arr[j]从右向左扫描已排序部分寻找插入位置。两个终止条件j0已到达数组最左端current arr[j]找到第一个小于current的元素元素移动arr[j1] arr[j]将大于current的元素向右移动一位插入操作arr[j1] current将current放入正确位置2.2 边界情况处理实际编码时容易踩的坑空数组需要添加if not arr: return的判断重复元素current arr[j]中的保证了算法的稳定性相等时不交换已排序数组通过current arr[j]条件提前终止内层循环测试不同边界条件print(insertion_sort([])) # [] print(insertion_sort([1])) # [1] print(insertion_sort([1,1,1])) # [1,1,1]3. 时间复杂度分析3.1 最坏情况 vs 最好情况插入排序的性能表现差异极大最坏情况逆序数组每次插入都需要移动所有已排序元素比较次数 1 2 ... (n-1) n(n-1)/2 →O(n²)最好情况已排序数组每次只需比较一次就结束内层循环比较次数 n-1 →O(n)平均情况需要移动一半的已排序元素 →O(n²)实测对比使用timeit模块from timeit import timeit best_case list(range(1000)) worst_case list(range(1000,0,-1)) print(最好情况:, timeit(lambda: insertion_sort(best_case), number100)) print(最坏情况:, timeit(lambda: insertion_sort(worst_case), number100))3.2 为何在小数据量时表现优异虽然时间复杂度是O(n²)但当n较小时通常n100常数项系数小内存访问局部性好没有递归开销Python内置的Timsort算法在小数组时就会切换到插入排序4. 优化策略与实践技巧4.1 二分查找优化内层循环的线性查找可以改用二分查找将比较次数优化到O(nlogn)但整体仍是O(n²)def binary_insertion_sort(arr): for i in range(1, len(arr)): current arr[i] # 使用bisect找到插入位置 left, right 0, i while left right: mid (left right) // 2 if current arr[mid]: right mid else: left mid 1 # 移动元素 for j in range(i, left, -1): arr[j] arr[j-1] arr[left] current4.2 实际应用场景适合使用插入排序的情况数据基本有序如日志按时间近乎有序小规模数据Python的list.sort()在n64时使用链表结构移动元素成本低不适合的场景大规模随机数据对稳定性无要求的排序5. 与其他排序算法的对比5.1 对比冒泡排序和选择排序通过表格看三种O(n²)算法的差异算法最好情况最坏情况空间复杂度稳定性特点插入排序O(n)O(n²)O(1)稳定对近乎有序数据效率极高冒泡排序O(n)O(n²)O(1)稳定实现简单但效率通常最低选择排序O(n²)O(n²)O(1)不稳定交换次数最少n-1次5.2 进阶算法的过渡插入排序是理解更复杂算法的基础希尔排序分组进行插入排序归并排序小规模子数组用插入排序Timsort结合了归并排序和插入排序在Python解释器中测试import random arr [random.randint(0,100) for _ in range(50)] %timeit sorted(arr) # Python内置的Timsort %timeit insertion_sort(arr) # 纯插入排序6. 经典变体希尔排序希尔排序是插入排序的改进版通过分组插入提升效率def shell_sort(arr): n len(arr) gap n // 2 while gap 0: for i in range(gap, n): temp arr[i] j i while j gap and arr[j-gap] temp: arr[j] arr[j-gap] j - gap arr[j] temp gap // 2关键改进点先对大间隔的元素排序使数组基本有序逐渐缩小间隔直至1变成标准插入排序时间复杂度取决于间隔序列最好可达O(nlog²n)7. 从Python实现看算法优化7.1 使用bisect模块简化Python标准库的bisect模块提供了二分查找实现from bisect import bisect_left def insertion_sort_bisect(arr): for i in range(1, len(arr)): current arr[i] pos bisect_left(arr, current, 0, i) arr[pos1:i1] arr[pos:i] arr[pos] current7.2 性能对比实验用Jupyter Notebook进行性能测试import numpy as np import pandas as pd from IPython.display import display sizes [10, 50, 100, 500] results [] for n in sizes: arr np.random.randint(0, 1000, n) t1 %timeit -o -q insertion_sort(arr.copy()) t2 %timeit -o -q binary_insertion_sort(arr.copy()) results.append([n, t1.average, t2.average]) df pd.DataFrame(results, columns[Size, Standard, Binary]) display(df.style.bar(alignmid))8. 实际工程中的应用思考在真实项目中应用插入排序时考虑数据特性如果数据已经部分有序插入排序可能优于O(nlogn)算法内存考虑对于内存受限环境插入排序的O(1)空间优势明显混合使用像Timsort那样与其他算法结合发挥各自优势一个实际案例数据库查询结果的分页排序当用户翻页时新数据可以插入到已排序的结果集中