深入理解【插入排序】:原理、实现与优化
-
- 一、算法概述
- 二、算法原理详解
-
- 核心思想
- 算法流程图示
-
- [初始数组:[7, 6, 9, 3, 1]](#初始数组:[7, 6, 9, 3, 1])
- 第一轮:插入6(i=1)
- 第二轮:插入9(i=2)
- 第三轮:插入3(i=3)
- 第四轮:插入1(i=4)
- 三、Java实现与优化
- 四、算法分析
- 五、实际应用场景
- 六、与其他排序算法的对比
- 七、扩展思考
- 总结
|-----------------------------|
| 🌺The Begin🌺点点关注,收藏不迷路🌺 |
一、算法概述
插入排序(Insertion Sort)是一种简单直观的基于比较的排序算法,其核心思想类似于我们整理扑克牌的方式。算法通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
基本特性
- 时间复杂度 :
- 最优情况(已排序数组):O(n)
- 最坏情况(逆序数组):O(n²)
- 平均情况:O(n²)
- 空间复杂度:O(1)(原地排序)
- 稳定性:稳定排序算法
二、算法原理详解
核心思想
- 将数组分为已排序 和未排序两部分
- 初始时已排序部分只包含第一个元素
- 每次从未排序部分取出第一个元素,与已排序部分的元素从后向前比较
- 找到合适位置后插入,使已排序部分保持有序
- 重复直到所有元素都被处理
算法流程图示
初始数组:[7, 6, 9, 3, 1]
markdown
┌───┬───┬───┬───┬───┐
│ 7 │ 6 │ 9 │ 3 │ 1 │
└───┴───┴───┴───┴───┘
▲
已排序部分
第一轮:插入6(i=1)
markdown
[比较6和7]
┌───┬───┬───┬───┬───┐
│ 7 │ 6 │ 9 │ 3 │ 1 │
└───┴───┴───┴───┴───┘
▲
6 < 7 → 交换
[结果]
┌───┬───┬───┬───┬───┐
│ 6 │ 7 │ 9 │ 3 │ 1 │
└───┴───┴───┴───┴───┘
▲
已排序部分
第二轮:插入9(i=2)
markdown
[比较9和7]
┌───┬───┬───┬───┬───┐
│ 6 │ 7 │ 9 │ 3 │ 1 │
└───┴───┴───┴───┴───┘
▲
9 > 7 → 不交换
[结果]
┌───┬───┬───┬───┬───┐
│ 6 │ 7 │ 9 │ 3 │ 1 │
└───┴───┴───┴───┴───┘
▲
已排序部分
第三轮:插入3(i=3)
markdown
[步骤1:比较3和9]
┌───┬───┬───┬───┬───┐
│ 6 │ 7 │ 9 │ 3 │ 1 │
└───┴───┴───┴───┴───┘
▲
3 < 9 → 交换
[步骤2:比较3和7]
┌───┬───┬───┬───┬───┐
│ 6 │ 7 │ 3 │ 9 │ 1 │
└───┴───┴───┴───┴───┘
▲
3 < 7 → 交换
[步骤3:比较3和6]
┌───┬───┬───┬───┬───┐
│ 6 │ 3 │ 7 │ 9 │ 1 │
└───┴───┴───┴───┴───┘
▲
3 < 6 → 交换
[最终结果]
┌───┬───┬───┬───┬───┐
│ 3 │ 6 │ 7 │ 9 │ 1 │
└───┴───┴───┴───┴───┘
▲
已排序部分
第四轮:插入1(i=4)
markdown
[完整过程]
┌───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┐
│ 3 │ 6 │ 7 │ 9 │ 1 │ → │ 1 │ 3 │ 6 │ 7 │ 9 │
└───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┘
▲ ▲
经过4次比较和交换 排序完成
三、Java实现与优化
基础实现
java
public class InsertionSort {
public static void sort(Comparable[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
for (int j = i; j > 0 && less(arr[j], arr[j-1]); j--) {
swap(arr, j, j-1);
}
}
}
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void swap(Object[] arr, int i, int j) {
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
优化版本(减少元素移动)
java
public static void optimizedSort(Comparable[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
Comparable key = arr[i];
int j = i-1;
// 将比key大的元素后移
while (j >= 0 && less(key, arr[j])) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = key;
}
}
性能对比
| 实现方式 | 比较次数 | 交换/移动次数 | 适合场景 |
|---|---|---|---|
| 基础实现 | O(n²) | O(n²) | 教学示例 |
| 优化实现 | O(n²) | O(n) | 实际应用 |
四、算法分析
时间复杂度分析
-
最佳情况:数组已经有序
- 每次只需要比较一次,共n-1次比较
- 时间复杂度:O(n)
-
最坏情况:数组完全逆序
- 需要进行1+2+...+(n-1)=n(n-1)/2次比较和交换
- 时间复杂度:O(n²)
-
平均情况:O(n²)
空间复杂度
- 只需要常数级别的额外空间(用于临时存储)
- 空间复杂度:O(1)
五、实际应用场景
- 小规模数据排序:当n较小时(通常n<50),插入排序性能优于更复杂的算法
- 近乎有序的数据:对于大部分已排序的数据效率接近O(n)
- 作为高级算法的组成部分 :
- 快速排序在小规模子数组时切换到插入排序
- Timsort中的插入排序优化
六、与其他排序算法的对比
| 算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 |
| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 |
七、扩展思考
- 二分插入排序:在查找插入位置时使用二分查找,可将比较次数降至O(nlogn),但移动次数仍为O(n²)
- 希尔排序:插入排序的改进版,通过分组插入排序来提高效率
- 链表实现:插入排序特别适合链表结构,因为不需要大量移动元素
总结
java
package com.kwan.springbootkwan.controller;
/**
* 插入排序
*/
public class InsertionSort {
/**
* 基础版本插入排序
* @param arr 待排序数组
*/
public static void basicSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
int n = arr.length;
// 从第二个元素开始(i=1),因为第一个元素默认是有序的
for (int i = 1; i < n; i++) {
// 将当前元素与已排序部分的元素从后往前比较
for (int j = i; j > 0; j--) {
// 如果当前元素比前一个元素小,则交换
if (arr[j] < arr[j - 1]) {
swap(arr, j, j - 1);
} else {
// 否则说明已经找到合适位置,可以提前终止内层循环
break;
}
}
}
}
// 交换数组中两个元素的位置
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 打印数组
public static void printArray(int[] arr) {
for (int num : arr) {
System.out.print(num + " ");
}
System.out.println();
}
// 测试代码
public static void main(String[] args) {
// 测试基础版本
int[] arr = {7, 6, 9, 3, 1};
System.out.println("原始数组:");
printArray(arr);
System.out.println("\n基础版本排序后:");
basicSort(arr);
printArray(arr);
}
}
插入排序虽然简单,但它体现了分治思想(将问题分为已排序和未排序部分),是理解更复杂算法的基础。其最佳情况下的线性时间复杂度使其在特定场景下非常高效,而简单的实现方式也使其成为算法教学的经典案例。

|---------------------------|
| 🌺The End🌺点点关注,收藏不迷路🌺 |