0. 原理
插入排序和冒泡排序以及选择排序的原理也比较相似,不过和他们元素交换的思想有点不同,就是插入不再是元素之间的交换,而是将排序的过程转换为在有序序列中插入元素的过程。具体的流程如下(方便理解,我们默认将序列从小到大排列):
- 第一个数字看成是一个有序序列
- 取出第二个数字插入到第一个元素中,如果第二个数字大于第一个数字,则数字位置不变,否则则插入到第一个数字前面。
- 重复上述过程,取出第三个数字,根据和前两个各个数字的大小比较,判断插入到哪个数字中。
- 直到取到倒数第二个数字,排序结束
对[2, 8, 1, 5, 3]这个数组排序,模拟流程如下:
round 1 start
round 1 finish
round 2 start
2 8 1 5 3 (exchange data: 1 <-> 8) 2 1 8 5 3
2 1 8 5 3 (exchange data: 1 <-> 2) 1 2 8 5 3
round 2 finish
round 3 start
1 2 8 5 3 (exchange data: 5 <-> 8) 1 2 5 8 3
round 3 finish
round 4 start
1 2 5 8 3 (exchange data: 3 <-> 8) 1 2 5 3 8
1 2 5 3 8 (exchange data: 3 <-> 5) 1 2 3 5 8
round 4 finish
- 第一轮,由于2,8已经有序,所以不需要交换。
- 第二轮,由于2,8,1中1的位置不对,因此通过两次交换放到第一个位置(相当于插入过程)
1. 实现
@Override
public int[] sort(int[] data) {
if (data == null || data.length <= 1) {
return data;
}
for (int i = 1; i < data.length; i++) {
System.out.println("round " + i + " start");
for (int j = i; j > 0; j--) {
//利用相邻比较交换模拟了插入的过程
if (data[j] < data[j - 1]) {
swap(data, j, j - 1);
} else {
break;
}
}
System.out.println("round " + i + " finish");
}
return data;
}
代码结构和冒泡排序与选择排序很类似,两个循环,只不过内循环的比较和起止不一样。关键在于j=i这个开始序列设置,相当于将原来的有序序列往后扩展一个数字,而内循环的操作则有点类似于冒泡算法的内循环,两两相邻比较交换(这一块其实优化,下面会讲到)。
具体实现代码可看:
2. 复杂度
外层循环遍历n-1次,内层循环最好只需要一次判断,最差需要i次交换,数据比较一次,数据操作三次。
因此,最好的情况是序列已经是升序,则复杂度为O(n),最坏的情况是序列是逆序,需要O(n²)此排序,平均时间负责度为O(n²)。空间复杂度为O(1)。
3. 优化
插入排序很直观一个可以优化的点就是插入的过程,因为已知序列是一个有序序列,因此可以通过二分查找法找到适合插入的位置,虽然数组的移动还是需要同样的次数,但是比较的次数会显著减少。具体实现如下:
@Override
public int[] sort(int[] data) {
if (data == null || data.length <= 1) {
return data;
}
for (int i = 1; i < data.length; i++) {
System.out.println("round " + i + " start");
int insertIndex = binarySearch(data, i);
for (int j = i; j > insertIndex; j--) {
swap(data, j, j - 1);
}
System.out.println("round " + i + " finish");
}
return data;
}
private int binarySearch(int[] data, int i) {
int start = 0;
int end = i - 1;
while (start <= end) {
int middle = (start + end) / 2;
if (data[i] < data[middle]) {
end = middle - 1;
} else {
start = middle + 1;
}
}
return start;
}
具体实现代码可看: