基础排序算法(五)直接插入排序
一 直接插入排序
直接插入排序是一种非常直观且基础的排序算法,其核心思想类似于我们整理扑克牌。
1.1 特性总结
插入排序特性总结
| 特性 | 说明 | 
|---|---|
| 核心思想 | 将待排序序列视为一个有序表和一个无序表。每次从无序表中取出第一个元素,将其插入到有序表中的正确位置,直到无序表为空 | 
| 时间复杂度 | 最好情况 O(n):序列已有序 最坏情况 O(n²):序列完全逆序 平均情况 O(n²) | 
| 空间复杂度 | O(1)。是原地排序算法,只需要常数级别的额外空间 | 
| 稳定性 | 稳定。在比较时,只有当待插入元素小于前一个元素时才移动,相等时不移动,因此相等元素的相对顺序保持不变 | 
| 主要优势 | 实现简单;对于小规模或基本有序的数据效率很高;稳定;原地排序 | 
| 主要劣势 | 对于大规模随机数据,效率较低(O(n²)) | 
1.2 算法原理
直接插入排序的运作过程非常直观,下图以数组 [5, 2, 4, 6, 1, 3]为例,展示了其排序过程:

- 初始状态:将序列的第一个元素(例如5)视为一个已排序的有序区。其余元素(2, 4, 6, 1, 3)构成无序区。
- 遍历与插入:从无序区取出第一个元素(例如2),将其与有序区的元素从后向前依次比较。
- 移动元素:在有序区中,所有比待插入元素大的元素都向后移动一个位置,为待插入元素腾出空间。
- 插入元素:将待插入元素放入腾出的空位。
- 重复:重复步骤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) 收藏 举报
 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号