直接插入排序(Insert Sort)
将数组中的数据分为两个区域,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想就是取未排序区间中的元素,在已排序区间中从后往前找到最合适的位置将其插入,并保证已排序区间数据一直有序,重复这个过程,直到未排序区间中元素未空,算法结束。
以上插入排序也包含两种操作,元素比较和元素移动。当需要将数据 a 插入到已排序区间时,需要拿 a 与已排序区间元素依次比较大小,找到合适的位置,找到插入点之后还需要将插入点之后的元素往后移动才能腾出位置给元素 a。
对于不同的查找插入点方法(从头到尾、从尾到头),元素的比较次数是有区别的。但对于一个给定的初始序列,移动操作的次数总是固定的,就等于初始逆序度。如上图,满有序度是 n*(n-1)/2=15,初始序列的有序度是 5,所以逆序度是 10。插入排序中,数据移动的个数总和也等于 10=3+3+4。
// 插入排序,n表示数组大小
public void insertionSort(int[] arr, int n) {
if (n < 1) {
return;
}
for (int i = 1; i < n; i++) {
// value 待插入的数
int value = arr[i];
// 插入的位置当然是从 i 前开始啦
int j = i - 1;
// 查找插入的位置
for (; j >= 0; j--) {
if (arr[j] > value) {
arr[j+1] = arr[j]; // 数据移动
} else {
break;
}
}
a[j+1] = value; // 插入元素
}
}
小结:
- 插入排序是原地排序算法吗
是,从实现过程可以明显看出,插入排序算法的运行并不需要额外的存储空间,所有空间复杂度是 O(1) - 插入排序是稳定的排序算法吗?
是,在插入排序中,对于值相同的元素,可以选择将后面出现的元素,插入到前面出现元素的后,这样 就可以保持原有的前后顺序不变。 - 插入排序的时间复杂度是多少?
最好情况:元素是有序的,不需要搬移,只用从尾到头在有序数组里面找插入位置,每次只需要比较一 个数据就能确定插入位置。O(n)
最坏情况:数组倒序,每次插入都相当于在数组的第一个位置插入新的数据,大量移动元素,O(n^2)
平均时间复杂度:对于在数组中插入一个数据的平均时间复杂度是 O(n),对于插入排序,每次插入操作 都相当于在数组中插入一个数据,循环执行 n 次,所以平均时间复杂度是 O(n^2) - 应用场景:数据规模小且接近有序
插入排序的优化—希尔排序
也称为缩小增量排序,与直接插入排序不同,会优先比较距离较远的元素。是把数据按一定的增量分组,对每组使用直接插入排序算法,随着增量逐渐减少,每组数越来越多,直到增量减至1,整个数组数据被分为一组,算法终止。
关于gap的取法其实是没有定论的,但是,大部分会在二分之一到三分之一附近。
详细代码:
public int[] ShellSort(int[] array) {
int len = array.length;
int temp, gap = len / 2;
while (gap > 0) {
for (int i = gap; i < len; i++) {
temp = array[i];
int preIndex = i - gap;
while (preIndex >= 0 && array[preIndex] > temp) {
array[preIndex + gap] = array[preIndex];
preIndex -= gap;
}
array[preIndex + gap] = temp;
}
gap /= 2;
}
return array;
}
小结:
-
应用场景:元素比较凌乱,个数比较多; 要求采用插入排序的思想对这个场景排序
做法:将序列变成有序,将数据变少 ---->分组(gap/3+1)、平均分组、每组应用插入排序 -
算法的稳定性:不稳定,看一个排序算法是否稳定:该排序算法在进行的过程中是否跨元素进行交换或 插入,有则不稳定
-
空间复杂度:O(1);
-
时间复杂度:
最好(数据有序) O(n)
最坏(比较难构造) O(n^2)