常见的排序算法有冒泡排序,选择排序,插入排序,希尔排序,快速排序,归并排序,堆排序。
其中,各算法的时间复杂度和空间复杂度如下表所示
算法 | 时间复杂度(平均情况) | 空间复杂度 | 时间复杂度(最坏情况) | 算法稳定性 |
---|---|---|---|---|
冒泡排序 | N*N | 1 | N*N | 稳定 |
选择排序 | N*N | 1 | N*N | 不稳定? |
插入排序 | N*N | 1 | N*N | 稳定? |
希尔排序 | N*logN ~ N*N | |||
快速排序 | N*logN | |||
归并排序 | N*logN | |||
堆排序 | N*logN | |||
桶排序 |
一、冒泡排序
1.1、简介
冒泡排序的基本思想是比较相邻的两个元素,并将较大者交换到后面。由于排序的过程中,元素按照由大到小的顺序依次上浮到其各自的位置,犹如一个一个的泡泡往上冒,因此叫做冒泡排序。
每一轮比较,可以确定一个元素的位置。共有N个数的数组,一共需要N-1轮比较完成排序。第一轮比较需要比较N-1次,第二轮需要比较N-2次,......,第N-1轮比较只需要比较1轮比较。需要比较的总的次数大约为N(N-1)/2。交换的次数不一定。
所以,冒泡排序的时间复杂度为O(N*N),空间复杂度为O(1)。
1.2、Java实现
public static void bubbleSort(int[] a) {
int n = a.length;
for(int i=1;i<n;i++) {
for(int j=0;j<n-i;j++) {
if(a[j] > a[j+1])
exch(a, j, j+1);
}
}
}
private void exch(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
二、选择排序
2.1、简介
选择排序的基本思想是:首先,找到数组中最小的那个元素,然后将其与数组中的第一个元素交换位置;然后,在剩下的元素中找到最小的元素,将其与数组中的第二个元素交换位置;如此继续下去,直到将整个数组排序。
因此,共需要N轮外循环,每一轮外循环确定一个元素的位置。N轮外循环之后,所有的元素的位置都已确定,完成排序。
在每一轮外循环中,为了确定剩下未排序元素中的最小者,需要进行一次内循环对未排序的元素进行一次遍历。第一轮外循环时,内循环中需要遍历的数量为N-1;第二轮外循环时,内循环中需要遍历的数量为N-2;一直到第N轮外循环时,内循环中需要遍历的数量为0。
因此,选择排序需要大约0+1+2+3+......+(N-1) = N*N/2次比较及N次交换,其时间复杂度为平方级别,O(N*N),空间复杂度为O(1)。
另外,由于选择排序还有两个特点:
1、运行时间与输入无关。因为无论输入数据是何种顺序,选择排序每一轮都会进行一次遍历以未排序数据中的选出最大者。
2、数据移动是最少的。只需要N次交换,与数组大小成线性关系(其他算法都是线性对数或平方级别)。
2.2、Java实现
public void selectSort(int[] a) {
int n = a.length;
for(int i=0;i<n;i++) {
int min = i;
for(int j = i+1;j<n;j++) {
if(a[j] < a[min])
min = j;
}
exch(a, i, min);
}
}
三、插入排序
3.1、简介
插入排序的基本思想如下:像麻将一样,从左到右第二个开始,将其与前一个比较,如果小于前一个,就与前者交换位置,直到不再小于前者或者它自己已经处于第一个位置。
插入排序适合于已经部分有序的数组,事实上,当数组的绝大部分元素都已经有序,只是少数元素无序时,选择插入排序是最好的选择。
对于小规模的数组,也可以优先选用插入排序,而非选择后面提到的高级排序算法,因为可以避免递归栈过于太深。
最好情况下,插入排序能够在线性时间内完成排序;最坏情况下,仍然需要平方级别的时间。
所以,插入排序的时间复杂度为O(N*N)。
3.2、Java实现
public void insertSort(int[] a) {
int n = a.length;
for(int i=1;i<n;i++) {
for(int j=i;j>0 && a[j] < a[j-1];j--) {
exch(a, j, j-1);
}
}
}
四、希尔排序
4.1、简介
当数组规模较大时,插入排序将元素移到合适位置的速度太慢。希尔排序就是针对这一问题的改进,其基本思想是将一个大规模的数组看成若干个长度为h的子数组相互交织而成。首先实现数组的h有序,当h较大时,可以较大范围内移动元素的位置;当h为1时,就相当于是完成了插入排序。
希尔排序的时间复杂度难以衡量,但可以确定的是介于平方级别与线性对数之间的。
4.2、Java实现
public static void shellSort(int[] a) {
int n = a.length;
int h = 1;
while(h < n/3)
h = h * 3 + 1;
while(h > 0) {
for(int i=h;i<n;i++) {
for(int j=i;j>=h && a[j] < a[j-h];j = j-h) {
exch(a, j, j-h);
}
}
h = h / 3;
}
}
五、快速排序
5.1、简介
5.2、Java实现
public static void quickSort(int[] a) {
quickSort(a, 0, a.length-1);
}
private static void quickSort(int[] a, int lo, int hi) {
if(hi <= lo)
return;
int j = partition(a, lo, hi);
quickSort(a, lo, j-1);
quickSort(a, j+1, hi);
}
private static int partition(int[] a, int lo, int hi) {
int v = a[lo];
int i = lo;
int j = hi+1;
while(true) {
while(a[++i] < v)
if(i == hi)
break;
while(a[--j] > v)
if(j == lo)
break;
if(i >= j)
break;
exch(a, i, j);
}
exch(a, lo, j);
return j;
}
六、归并排序
5.1、简介
5.2、Java实现
5.2.1、自上而下
/*
* 归并排序,自上而下
*/
private static int[] aux;
public static void mergeSort1(int[] a) {
aux = new int[a.length];
mergeSort(a, 0, a.length-1);
}
private static void mergeSort(int[] a, int lo, int hi) {
if(hi <= lo)
return;
int mid = lo + (hi - lo)/2;
mergeSort(a, lo, mid);
mergeSort(a, mid+1, hi);
merge(a, lo, mid, hi);
}
private static void merge(int[] a, int lo, int mid, int hi) {
int i = lo;
int j = mid + 1;
for(int k=lo;k<=hi;k++) {
aux[k] = a[k];
}
for(int k=lo;k<=hi;k++) {
if(i > mid)
a[k] = aux[j++];
else if(j > hi)
a[k] = aux[i++];
else if(aux[i] < aux[j])
a[k] = aux[i++];
else
a[k] = aux[j++];
}
}
5.2.2、自下而上
/*
* 归并排序,自下而上
*/
public static void mergeSort2(int[] a) {
int n = a.length;
aux = new int[n];
for(int sz = 1;sz < n; sz = sz + sz) {
for(int lo = 0; lo < n-sz; lo = lo + sz + sz) {
merge(a, lo, lo + sz-1, Math.min(lo + sz + sz -1, n-1));
}
}
}