基础排序算法(五)直接插入排序

一 直接插入排序

直接插入排序是一种非常直观且基础的排序算法,其核心思想类似于我们整理扑克牌。

1.1 特性总结

插入排序特性总结

特性 说明
核心思想 将待排序序列视为一个有序表和一个无序表。每次从无序表中取出第一个元素,将其插入到有序表中的正确位置,直到无序表为空
时间复杂度 最好情况 O(n):序列已有序
最坏情况 O(n²):序列完全逆序
平均情况 O(n²)
空间复杂度 O(1)。是原地排序算法,只需要常数级别的额外空间
稳定性 稳定。在比较时,只有当待插入元素小于前一个元素时才移动,相等时不移动,因此相等元素的相对顺序保持不变
主要优势 实现简单;对于小规模或基本有序的数据效率很高;稳定;原地排序
主要劣势 对于大规模随机数据,效率较低(O(n²))

1.2 算法原理

直接插入排序的运作过程非常直观,下图以数组 [5, 2, 4, 6, 1, 3]为例,展示了其排序过程:
图片

  1. 初始状态​​:将序列的第一个元素(例如5)视为一个已排序的有序区。其余元素(2, 4, 6, 1, 3)构成无序区。
  2. ​​遍历与插入​​:从无序区取出第一个元素(例如2),将其与有序区的元素​​从后向前​​依次比较。
  3. ​​移动元素​​:在有序区中,所有比待插入元素大的元素都向后移动一个位置,为待插入元素腾出空间。
  4. ​​插入元素​​:将待插入元素放入腾出的空位。
  5. ​​重复​​:重复步骤2-4,直到无序区为空,整个序列变为有序。

1.3 复杂度分析

1.3.1 时间复杂度分析​​:

  • ​​最好情况 O(n)​​:当输入序列已经是有序时,内层循环每次只比较一次就会发现待插入元素已在正确位置,因此需要进行 n-1 次比较,0次移动。这是最高效的情况。
  • ​​最坏情况 O(n²)​​:当输入序列完全逆序时,每次插入都需要与有序区中的所有元素比较并移动。总的比较和移动次数都与 n(n-1)/2 成正比,即 O(n²)。
  • ​​平均情况 O(n²)​​:对于随机排列的序列,平均每次插入需要移动有序区的一半元素,因此平均时间复杂度也为 O(n²)。

1.3.2 ​空间复杂度 O(1) 的由来​​:

  • 排序过程是在原数组上进行的,只需要固定数量的额外临时变量(如用于暂存待插入元素的 key或 temp,以及循环索引 i, j),不随待排序数据规模 n 的增大而增加额外空间。

1.4 使用场景

直接插入排序在以下特定场景中表现出色:

  • ​​小规模数据排序​​:当待排序元素数量很少(例如 n < 50)时,其实现简单的优势凸显,虽然时间复杂度是O(n²),但常数因子很小,实际效率可能比一些更复杂的O(n log n)算法更高。
  • ​​数据基本有序​​:如果序列已经大部分有序,需要进行的比较和移动操作会大幅减少,效率很高。
  • ​​作为高级算法的子过程​​:在一些复杂的排序算法中,如​​快速排序​​或​​归并排序​​,当递归分解到小规模子序列时,常会切换使用插入排序来优化整体性能。
  • ​​链式存储结构​​:该算法可以很好地应用于链表排序,因为插入操作在链表中只需要修改指针,无需像在顺序表中那样大量移动元素

1.5 代码实现

1.5.1 c语言实现

#include <stdio.h>

// 直接插入排序函数
// 参数: arr - 待排序数组指针, n - 数组长度
void insertionSort(int arr[], int n) {
    int i, j, key;
    // 从第二个元素开始遍历(下标1到n-1),因为第一个元素视为已排序
    for (i = 1; i < n; i++) {
        key = arr[i]; // 取出当前待插入的元素(无序区的第一个元素)
        j = i - 1;    // j指向有序区的最后一个元素

        // 在有序区中从后向前扫描,寻找key的正确插入位置
        // 同时将比key大的元素向后移动一位,为key腾出空间
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j]; // 将大于key的元素后移
            j--;                 // 指针前移,继续比较
        }
        // 跳出循环时,j+1的位置就是key应该插入的位置
        arr[j + 1] = key; // 将key插入到正确位置
    }
}

// 打印数组函数,用于测试
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// 主函数,测试排序算法
int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]); // 计算数组长度

    printf("排序前的数组: \n");
    printArray(arr, n);

    insertionSort(arr, n);

    printf("排序后的数组: \n");
    printArray(arr, n);

    return 0;
}

1.5.2 ​代码关键点解释

  • ​外层循环 (for i from 1 to n-1)​​:从第二个元素开始,因为第一个元素自然构成初始有序区。
  • ​​内层循环 (while j >= 0 and arr[j] > key)​​:这是算法的核心。它从有序区的末尾开始,将每个大于 key的元素向后移动一个位置,直到找到 key应该插入的位置(即找到第一个小于或等于 key的元素后面)。
  • ​​插入操作 (arr[j + 1] = key)​​:在找到的正确位置插入待排序元素。
  • ​​稳定性​​:内层循环的条件是 arr[j] > key,而不是 >=。这意味着当遇到相等的元素时,循环停止,key会插入到相等元素的后面,从而保证了排序的稳定性。

1.6 常用算法比较

排序算法全面比较

算法 平均时间复杂度 最好情况 最坏情况 空间复杂度 稳定性 主要特点与适用场景
直接插入排序 O(n²) O(n)(已有序) O(n²)(逆序) O(1) 稳定 小规模或基本有序数据;实现简单;稳定
冒泡排序 O(n²) O(n)(已有序,可优化) O(n²) O(1) 稳定 实现简单,但交换操作频繁,通常效率低于插入排序
简单选择排序 O(n²) O(n²) O(n²) O(1) 不稳定 交换次数少(最多n-1次),但比较次数固定为O(n²)。当元素移动成本高时可能有优势
快速排序 O(n log n) O(n log n) O(n²)(如已有序) O(log n) 不稳定 大规模随机数据平均性能最佳;但最坏情况性能差,且不稳定
归并排序 O(n log n) O(n log n) O(n log n) O(n) 稳定 性能稳定,稳定的O(n log n)排序,但需要O(n)额外空间,适合外部排序和对稳定性有高要求的场景

1.7 总结

直接插入排序是一种概念简单、编码容易的​​稳定​​排序算法,其​​原地排序​​的特性使其空间效率高(O(1))。它的性能​​强烈依赖于输入数据的初始状态​​:对于​​小规模数据​​或​​基本有序的序列​​,其效率非常高,甚至优于一些更复杂的算法;但对于​​大规模随机数据​​,其O(n²)的时间复杂度是主要瓶颈。
它的核心价值在于​​教学演示​​、作为​​更复杂算法(如希尔排序、快速排序混合使用)的组成部分​​,以及在特定应用场景(如小数据量、近乎有序、链表排序)下的实际应用。

posted on 2025-10-31 14:18  weiwei2021  阅读(0)  评论(0)    收藏  举报