一、从整理桌面开始理解插入排序

想象一下,你的书桌上堆满了各种书籍,有的叠在一边,有的散落在各处,看起来乱糟糟的。现在你想把它们按从左到右(比如从薄到厚、或从A到Z的书名顺序)排好。你会怎么做?

最直观的思路:先把最左边的一本书(比如《Python编程》)固定在原位,然后从剩下的书里随便拿一本(比如《数据结构》),看看它应该放在哪里——如果它比《Python编程》厚(假设按厚度排序),就放在《Python编程》右边;如果薄,就放在左边。接着再拿下一本书(比如《算法导论》),和已经排好的书比较,找到合适的位置插入……

这个过程是不是很像我们即将要讲的插入排序?插入排序的核心思想,就是把“待排序的元素”像整理桌面时的书一样,逐个插入到“已排序部分”的正确位置,直到所有元素都排好序。

二、插入排序的基本思想

插入排序的逻辑可以分为两步:
1. 初始化已排序部分:默认数组的第一个元素已经是“排好序的”(因为单个元素天然有序)。
2. 逐个插入未排序元素:从第二个元素开始(即数组索引1的位置),依次处理后面的每个元素。对于当前元素,将它与前面已排序的元素逐个比较,找到它应该插入的位置,然后将该位置之后的元素后移一位,把当前元素“插入”到正确位置。

三、用例子演示插入排序过程

假设我们要对数组 [5, 3, 8, 2, 4] 进行从小到大排序。我们用“整理桌面”的类比来一步步看:

初始状态:书桌上的书是 [5, 3, 8, 2, 4](数字代表书的厚度,假设5是最厚的,2是最薄的)。我们需要按厚度从小到大排列。

  • 第一步:第1本书(5)已经在原位,已排序部分是 [5]
  • 第二步:处理第2本书(3)。此时已排序部分是 [5],3比5薄,所以应该放在5左边。把5后移一位,插入3,现在已排序部分变为 [3, 5],未排序部分是 [8, 2, 4]
  • 第三步:处理第3本书(8)。已排序部分是 [3, 5],8比5厚,所以直接放在已排序部分末尾,已排序部分变为 [3, 5, 8],未排序部分是 [2, 4]
  • 第四步:处理第4本书(2)。已排序部分是 [3, 5, 8],需要找到2的位置:
  • 8比2厚,后移一位(数组变为 [3, 5, 8, 8, 4]?不对,这里需要明确:其实是从后往前比较,把已排序部分的元素后移,给当前元素腾出位置。正确过程是:
    • 2和8比较(8>2),8后移一位(数组变为 [3, 5, 8, 8, 4] ?不,数组是 [3,5,8,2,4],当前元素是2,i=3(索引从0开始),j从i-1=2开始(即8)。因为8>2,所以把8后移到i位置(即索引3),数组变为 [3,5,8,8,4]?不对,此时j=2,i=3,应该是 arr[j+1] = arr[j],即 arr[3] = arr[2](把8移到3的位置),j减1到1(5)。
    • 5>2,继续后移:arr[2] = arr[1](5移到2的位置),j减1到0(3)。
    • 3>2,继续后移:arr[1] = arr[0](3移到1的位置),j减1到-1(循环结束)。
    • 最后插入2到j+1=0的位置,数组变为 [2, 3, 5, 8, 4]
  • 第五步:处理第5本书(4)。已排序部分是 [2, 3, 5, 8],找到4的位置:
  • 8>4,后移:arr[4] = arr[3](8移到4的位置),j=2(5)。
  • 5>4,后移:arr[3] = arr[2](5移到3的位置),j=1(3)。
  • 3<4,停止后移,插入4到j+1=2的位置,数组变为 [2, 3, 4, 5, 8],排序完成。

四、插入排序的代码实现(Python)

用代码模拟上面的“整理桌面”过程,核心是:
- 外层循环:遍历需要插入的元素(从索引1开始)。
- 内层循环:从已排序部分的末尾向前比较,找到插入位置。

def insertion_sort(arr):
    # 外层循环:处理每个待插入的元素(从第二个元素开始)
    for i in range(1, len(arr)):
        current = arr[i]  # 当前要插入的元素
        j = i - 1         # 已排序部分的末尾索引(初始为i-1)

        # 内层循环:找到插入位置并后移元素
        while j >= 0 and current < arr[j]:
            arr[j + 1] = arr[j]  # 前面的元素比当前大,后移一位
            j -= 1               # 继续向前比较
        arr[j + 1] = current     # 插入当前元素到正确位置
    return arr

# 测试
arr = [5, 3, 8, 2, 4]
sorted_arr = insertion_sort(arr)
print("排序后:", sorted_arr)  # 输出:排序后: [2, 3, 4, 5, 8]

五、关键点总结

  1. 核心思想:像整理桌面一样,将元素逐个插入到已排序部分的正确位置,而非通过交换。
  2. 时间复杂度:O(n²)(n为数组长度),适合小规模数据(比如几千个元素以内)。
  3. 空间复杂度:O(1)(原地排序,不需要额外数组)。
  4. 适用场景:数据量小、接近有序的数据(比如已部分排序的数组),或者对内存敏感的场景(空间复杂度低)。

六、和整理桌面的类比呼应

  • 已排序部分:相当于书桌上已经按顺序排好的区域。
  • 待插入元素:相当于你刚从书堆里拿出来的新书。
  • 比较与移动:相当于把新书和已排好的书比较,若位置不对就把后面的书“移开”,给新书腾出位置。

通过这种类比,插入排序的逻辑变得像“整理书桌”一样自然——不需要复杂的交换,只需要耐心地为每个元素找到正确的“小窝”。

记住:插入排序的本质是“逐个插入”,而非“整体交换”。掌握这个核心,再复杂的排序算法也能从简单的类比中找到突破口!

小夜