一、直接插入排序
思想:
在一个已排好序的序列基础上,每次将下一个待排序记录有序插入到已排好序的序列中,直到所有待排序记录全部插入为止。
步骤:
1. 刚开始排序时,可将数组第一个元素认为是已经有序(只有一个数时肯定有序)
2. 接着依次从数组第二个元素开始,到数组最后一个元素,每次顺次取出一个元素插入已排序序列中
3. 当待排序元素全部插入到已排序序列中时完成排序
注意:
直接插入排序是利用插入的思想进行排序,是在本数组里边完成的,不需要借助其他数组空间。所谓的插入元素指的是从数组第二个元素开始到数组最后一个元素(未排序序列),依次取出一个元素插入到数组已排序序列中。通俗来说,此时数组分为两部分:已排序序列+未排序序列。
待排序序列: 2 5 9 1 3 7
1) {2} 5 9 1 3 7 其中红色部分表示已排好序的部分
2) {2 5} 9 1 3 7 蓝色数字表示此次需要插入的元素
3) {2 5 9} 1 3 7 黑色部分表示待排序部分
4) {1 2 5 9} 3 7
5) {1 2 3 5 9} 7
6) {1 2 3 5 7 9}
public void insert() {
int i, j;
for (i = 1; i < len; i++) {//从数组第二个元素开始遍历数组(数组首元素已经有序)
int key = arr[i];//由于可能会将arr[i]之前的数顺序后移,此过程定会覆盖原来的arr[i],所以一定要在挪动元素之前把arr[i]保存起来
for (j = i - 1; j >= 0; j--) {
if (key > arr[j]) {//往前遍历的时候第一次遇到比key小的元素时就可以停下了
break;
}
arr[j + 1] = arr[j];//否则将key之前的元素顺序后移
}
arr[j + 1] = key;//将key插到相应位置
}
}
复杂度分析:
1.时间复杂度
最好情况:所给数组已经有序,此时只需要依次取出数组未排序序列的元素并直接插入,省去了移动元素的时间。所以此时的时间复杂度为O(n);
最坏情况:所给数组逆序,此时外层循环依次取出数组未排序序列的元素,内层循环将该元素之前的元素全部依次往后移动一个位置,即插入第 i 个元素时要移动 i -1 次。所以此时的时间复杂度为O(n^2);
所以,直接插入排序的时间复杂度为O(n^2)。
2.空间复杂度
由于完成此次排序只需要一个辅助空间,即存放key的空间,所以空间复杂度为O(1)。
稳定性判断:
从内层循环的条件语句if (key > arr[j])中我们可以知道,只有当要插入的元素比它之前的元素大时,才会移动之前的元素。当遇到和自己相等的元素时并不会进行移动操作,即要插入的元素不会出现在跟它相同的元素的前面 --> 所以,直接插入排序是一个稳定的排序算法。
待排序序列: 2 5 7 1 3 7
1) {2} 5 7 1 3 7 其中红色部分表示已排好序的部分
2) {2 5} 7 1 3 7 蓝色数字表示此次需要插入的元素
3) {2 5 7} 1 3 7 黑色部分表示待排序部分
4) {1 2 5 7} 3 7
5) {1 2 3 5 7} 7
6) {1 2 3 5 7 7}
适用场景:
由于直接插入排序最好情况下的时间复杂度为O(n),所以特别适用于待排序记录数目较少且基本有序的情况。
完整代码:
//直接插入排序
public class InsertSort {
private int[] arr;
private int len;
public InsertSort(int[] arr) {
this.arr = arr;
this.len = arr.length;
}
public void insert() {
int i, j;
for (i = 1; i < len; i++) {//从数组第二个元素开始遍历数组(数组首元素已经有序)
int key = arr[i];//由于可能会将arr[i]之前的数顺序后移,此过程定会覆盖原来的arr[i],所以一定要在挪动元素之前把arr[i]保存起来
for (j = i - 1; j >= 0; j--) {
if (key > arr[j]) {//往前遍历的时候第一次遇到比key小的元素时就可以停下了
break;
}
arr[j + 1] = arr[j];//否则将key之前的元素顺序后移
}
arr[j + 1] = key;//将key插到相应位置
}
}
public void print() {
for (int i = 0; i < len; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
二、希尔排序
思想:
希尔排序又称缩小增量排序,也是一种基于插入思想的排序方法。即每次排序不是按顺序排,而是以gap=gap/3+1的间隔排序(),其中gap初始值为size,在由间隔分割成的每个子序列进行插入排序。
步骤:
1. 以gap=gap/3+1为间隔,在整个待排序序列中将所有间隔为gap的记录分成一组,进行组内直接插入排序(gap初始值为数组大小)
2. 重复步骤1,直到gap为1,此时只有一个子序列,对该序列进行直接插入排序,完成排序。
待排序序列: 2 5 7 1 3 7
取gap=3,分为间隔为3的子序列: 2 5 7 1 3 7
各子序列内进行插入排序,结果为: 1 3 7 2 5 7
取gap=2,分为间隔为2的子序列: 1 3 7 2 5 7 其中相同颜色的记录表示同一个子序列
各子序列内进行插入排序,结果为: 1 2 5 3 7 7
取gap=1,分为间隔为1的子序列: 1 2 5 3 7 7
各子序列内进行插入排序,结果为: 1 2 3 5 7 7
public void shell() {
int gap = len;
while (true) {
gap = gap / 3 + 1;//间隔,增量
_shell(gap);
if (gap == 1) {
return;
}
}
}
private void _shell(int gap) {
int i, j;
for (i = gap; i >= len; i++) {
int key = arr[i];
for (j = i - gap; j >= 0; j -= gap) {
if (key > arr[j]) {
arr[j + gap] = arr[j];
} else {
break;
}
}
arr[j + gap] = key;
}
}
复杂度分析:
1.时间复杂度:
最好情况:所给数组已经有序,此时时间复杂度为O(n);
最坏情况:所给数组逆序,此时时间复杂度为O(n^2);
所以,希尔排序的时间复杂度为O(n^1.3)。
2.空间复杂度
由于排序过程中只需要一个辅助空间key,所以空间复杂度为O(1)。
稳定性判断:
希尔排序不稳定。如待排序序列为{2,4,1,2}。排序前2在 2 前面,排序后2在 2 后面。
待排序序列: 2 4 1 2
取gap=2,分为间隔为2的子序列: 2 4 1 2
各子序列内进行插入排序,结果为: 1 2 2 4
取gap=1,分为间隔为1的子序列: 1 2 2 4
各子序列内进行插入排序,结果为: 1 2 2 4
适用场景:
尽管当gap为1时相当于直接插入排序,但是由于此时序列已经基本有序,恰好是直接插入排序的最好情况,所以希尔排序是一个较好的排序方法,适用于对中等规模(n<=1000)的序列进行排序。
完整代码:
public class Shell {
private int[] arr;
private int len;
public Shell(int[] arr) {
this.arr = arr;
this.len = arr.length;
}
public void shell() {
int gap = len;
while (true) {
gap = gap / 3 + 1;//间隔,增量
_shell(gap);
if (gap == 1) {
return;
}
}
}
private void _shell(int gap) {
int i, j;
for (i = gap; i >= len; i++) {
int key = arr[i];
for (j = i - gap; j >= 0; j -= gap) {
if (key > arr[j]) {
arr[j + gap] = arr[j];
} else {
break;
}
}
arr[j + gap] = key;
}
}
public void print() {
for (int i = 0; i < len; i++) {
System.out.print(arr[i] + " ");
}
System.out.println("");
}
}