插入排序
插入排序也是一种比较直观和容易理解的排序算法,通过构建有序序列,将未排序中的数据插入到已排序中序列,最终未排序全部插入到有序序列,达到排序效果。 主要步骤:
- 将原始数据的第一个元素当成已排序序列,然后将除了第一个元素的后面元素当成未排序序列。
- 从后面未排序元素中从前到后扫描,挨个取出元素,在已排序的序列中从后往前扫描,将从未排序序列中取出的元素插入到已排序序列的指定位置。
- 当未排序元素数量为0时,则排序完成。
动图演示
/**
* 插入排序
*
* @param array
*/
public void inserSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int sortItem = array[i];
int j = i;
// 将当前元素插入到前面的有序元素里,将当前元素与前面有序元素从后往前挨个对比,然后将元素插入到指定位置。
while (j > 0 && sortItem < array[j - 1]) {
array[j] = array[j - 1];
j--;
}
// 若当前元素在前面已排序里面不是最大的,则将它插入到前面已经确定了位置里。
if (j != i) {
array[j] = sortItem;
}
}
}
运行:
@Test
public void addition_isCorrect() throws Exception {
int[] array = new int[]{3, 2, 5, 8, 1, 9, 4, 6, 7};
for (int i : array) {
System.out.print(i + " ");
}
System.out.println("\n");
selectSort(array);
bubbleSort(array);
inserSort(array);
for (int i : array) {
System.out.print(i + " ");
}
}
时间复杂度就是O(n^2),因为基本上每个元素都要处理多次,需要反复将已排序元素移动,然后将数据插入到指定的位置
优化一
可以看出,上面的写法性能很差,主要是交换次数太多了。
解决思路:将 交换 改成 挪动:
- 将待插入的元素备份。
- 将头部元素中比待插入元素大的都往后 挪动一位。
- 将备份元素插入合适的位置。
/**
* 插入排序
*
* @param array
*/
public void inserSort2(int[] array) {
for (int begin = 1; begin < array.length; begin++) {
int element = array[begin];
int cur = begin;
// 将当前元素插入到前面的有序元素里,将当前元素与前面有序元素从后往前挨个对比,然后将元素插入到指定位置。
while (cur > 0 && element < array[cur - 1]) {
array[cur] = array[--cur];
}
array[cur] = element;
}
}
优化二
可以通过优化 比较次数来达到优化目的。 从头开始扫描每个元素,每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然有序。
之前找到合适的位置都是通过for循环遍历一遍,时间复杂度是O(n)。我们可以通过优化这查找位置的方法来提升时间。
注意到 头部序列其实是排序好的,所以我们可以通过二分搜索来查找合适的位置。 二分查找的时间复杂度是O(logN)。
/**
* 找到index元素待插入的位置
* <p>
* 已经排好的区域是[0,index)
*
* @param index 待插入元素的位置
* @return 应该插入的位置
*/
private int search(int[] array, int index) {
int v = array[index];
int begin = 0;
int end = index;
while (begin < end) {
int mid = (begin + end) >> 1;
if (v < array[mid]) {
end = mid;
} else {
begin = mid + 1;
}
}
return begin;
}
注意上面的代码和二分搜索有点不一样。搜索插入的位置,应该是最近大于待插入元素的位置。 知道应该插入的位置之后,我们还需要将从该位置到尾部的数据都往后移动一位,腾出位置插入目标值。
private void insert(int[] array, int begin, int insertIndex) {
//备份
int element = array[begin];
//将[insertIndex,begin)位置像后挪
for (int i = begin; i > insertIndex; i--) {
array[i] = array[i - 1];
}
array[insertIndex] = element;
}
整体代码是:
private void inserSort3(int[] array) {
for (int begin = 1; begin < array.length; begin++) {
//头部是有序的,使用二分查找,复杂度就是O(logn)
int insertIndex = search(array, begin);
insert(array, begin, insertIndex);
}
}
private void insert(int[] array, int begin, int insertIndex) {
//备份
int element = array[begin];
//将[insertIndex,begin)位置像后挪
for (int i = begin; i > insertIndex; i--) {
array[i] = array[i - 1];
}
array[insertIndex] = element;
}
/**
* 找到index元素待插入的位置
* <p>
* 已经排好的区域是[0,index)
*
* @param index 待插入元素的位置
* @return 应该插入的位置
*/
private int search(int[] array, int index) {
int v = array[index];
int begin = 0;
int end = index;
while (begin < end) {
int mid = (begin + end) >> 1;
if (v < array[mid]) {
end = mid;
} else {
begin = mid + 1;
}
}
return begin;
}