插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因为在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
/**
* 插入
*
* @param array
*/
private static void insertionSort(int[] array) {
if (array == null || array.length == 0 || array.length == 1)
return;
int insertVal;//插入的元素
int index;//被插入的位置
// 默认array[0]是有序的,将剩下的插入到该有序数组中
for (int i = 1; i < array.length; i++) {
insertVal = array[i];
index = i;
// 如果要插入的元素小于第index-1个元素,就将第index-1个元素向后移动
while (index > 0 && insertVal < array[index - 1]) {
array[index] = array[index - 1];
index--;
}
array[index] = insertVal;//放置insertVal
}
}
最好的情况:正序有序(从小到大),这样只需要比较 n 次,不需要移动。因此时间复杂度为\(O(n)\)。
最坏的情况:逆序有序,这样每一个元素就需要比较 n 次,共有 n 个元素,因此实际复杂度为 \(O(n^2)\)。
平均情况:\(O(n^2)\)。
空间复杂度:\(O(1)\)。
如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
插入排序 | \(O(n^2)\) | \(O(n)\) | \(O(n^2)\) | \(O(1)\) | 稳定 |
二分(折半)插入排序是一种在直接插入排序算法上进行改动的排序算法。其与直接排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有一定提升。
/**
* 插入优化(二分法)
*
* @param array
*/
private static void insertionSort_2(int[] array) {
if (array == null || array.length == 0 || array.length == 1)
return;
int insertVal;//插入的元素
int left;
int right;
int middle;
// 默认array[0]是有序的,将剩下的插入到该有序数组中
for (int i = 1; i < array.length; i++) {
insertVal = array[i];
left = 0;
right = i - 1;
while (left <= right) {
middle = (left + right) >> 1;
//如果待插入元素比中间元素小
if (array[middle] > insertVal) {
// 插入点在左
right = middle - 1;
} else {
// 插入点在右
left = middle + 1;
}
}
//将前面所有大于当前待插入元素的元素后移
for (int j = i - 1; j >= left; j--) {
array[j + 1] = array[j];
}
//将待插入元素插入到正确位置
array[left] = insertVal;
}
}
插入每个元素需要\(O(log n)\)次比较,最多移动 i+1 次,最少 2 次。
最好情况时间复杂度为\(O(n log n)\),最坏和平均情况时间复杂度为\(O(n^2)\)。
二分搜索比顺序搜索查找快,所以二分插入排序就平均性能来说比直接插入排序要快。
当n较大时,总排序码比较次数比直接插入排序的最坏情况要好得多,但比其最好情况要差。
在对象的初始排列已经按排序码排好序或接近有序时,直接插入排序比折半插入排序执行的排序码比较次数要少。折半插入排序的对象移动次数与直接插入排序相同,依赖于对象的初始排列。
二分插入排序是一个稳定的排序方法。