一、算法基础
1.1 什么是插入排序
插入排序是一种简单直观的排序算法,它的工作原理类似于我们打牌时整理手牌的过程。插入排序的核心思想是将数组分为已排序和未排序两部分,每次从未排序部分取出一个元素,插入到已排序部分的适当位置。
1.2 插入排序的基本思想
- 将第一个元素视为已排序部分
- 取出下一个元素,在已排序部分找到合适的插入位置
- 将已排序部分中大于该元素的所有元素后移一位
- 将该元素插入到正确的位置
- 重复步骤2~4,直到所有元素都插入到正确的位置
1.3 时间复杂度
- 最佳情况:O(n),当数组已经排好序时
- 最坏情况:O(n²),当数组逆序排列时
- 平均情况:O(n²)
- 空间复杂度:O(1),原地排序算法
二、插入排序的实现
2.1 标准实现
java
public class InsertionSort {
public static void sort(int[] arr) {
int n = arr.length;
// 从第二个元素开始,第一个元素被视为已排序
for (int i = 1; i < n; i++) {
// 取出当前元素
int key = arr[i];
int j = i - 1;
// 将比key大的元素向后移动
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
// 将key插入到正确位置
arr[j + 1] = key;
}
}
}
2.2 优化实现(针对部分有序数组)
java
public class OptimizedInsertionSort {
public static void sort(int[] arr) {
int n = arr.length;
// 找到第一个逆序位置
int startIndex = 1;
for (int i = 0; i < n - 1; i++) {
if (arr[i] > arr[i + 1]) {
startIndex = i + 1;
break;
}
}
// 从startIndex开始进行插入排序
for (int i = startIndex; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
}
2.3 二分查找优化
在插入过程中,可以使用二分查找来加速查找插入位置:
java
public class BinaryInsertionSort {
public static void sort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i];
// 使用二分查找找到插入位置
int insertPos = binarySearch(arr, 0, i - 1, key);
// 将元素后移
if (insertPos < i) {
System.arraycopy(arr, insertPos, arr, insertPos + 1, i - insertPos);
arr[insertPos] = key;
}
}
}
// 二分查找最小的不小于key的元素位置
private static int binarySearch(int[] arr, int low, int high, int key) {
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] > key) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return low;
}
}
三、插入排序的特点分析
3.1 优点
- 实现简单:算法逻辑直观,容易理解和实现
- 稳定排序:相同元素的相对位置在排序后保持不变
- 原地排序:不需要额外空间
- 对小规模数据高效:当数据量小时,常数因子小,效率高
- 对近乎有序的数据非常高效:接近O(n)时间复杂度
- 在线算法:可以一边读取数据一边排序
3.2 缺点
- 时间复杂度高:对于大规模数据,O(n²)的时间复杂度较高
- 不适合逆序数组:在逆序数组上性能最差
- 不适合频繁交换元素的场景:在交换代价高的情况下可能效率低下
四、实际应用示例
4.1 完整示例程序
java
public class InsertionSortDemo {
public static void main(String[] args) {
// 示例数组
int[] arr = {12, 11, 13, 5, 6, 7};
System.out.println("原始数组:");
printArray(arr);
// 执行插入排序
insertionSort(arr);
System.out.println("排序后数组:");
printArray(arr);
}
// 插入排序算法
public static void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
// 将比key大的元素向后移动
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
// 将key插入到正确位置
arr[j + 1] = key;
}
}
// 打印数组
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
4.2 排序过程示例
假设我们对数组 [5, 2, 4, 6, 1, 3]
进行插入排序:
- 初始状态:[5, 2, 4, 6, 1, 3](粗体表示已排序部分)
- 插入2:[2, 5, 4, 6, 1, 3](2比5小,所以插入到5前面)
- 插入4:[2, 4, 5, 6, 1, 3](4比5小,比2大,所以插入到位置2)
- 插入6:[2, 4, 5, 6, 1, 3](6比所有已排序元素都大,保持原位)
- 插入1:[1, 2, 4, 5, 6, 3](1比所有已排序元素都小,插入到开始位置)
- 插入3:[1, 2, 3, 4, 5, 6](3比2大,比4小,插入到位置2)
五、总结
插入排序是一种简单但实用的排序算法,尤其在以下场景中表现出色:
- 数据规模较小
- 数据已经部分有序
- 需要稳定排序
- 需要在线处理数据
虽然在最坏情况下时间复杂度为O(n²),但其简单性和在特定场景下的优势使其成为实际应用中的重要选择。在标准库中,很多高级排序算法在处理小规模子数组时,会转而使用插入排序,因为此时插入排序的常数因子优势明显。