1 前言
对于基本排序算法来说,时间复杂度一般都是O(n^2)。而高级排序算法的时间复杂度一般都是O(nlogn)。高级排序算法主要针对基本排序算法进行优化。下面介绍几种常见的高级排序算法,希尔排序,归并排序,快速排序,堆排序
2 希尔排序
希尔排序是插入排序的一个改进,它主要是用一个递增序列来使数组进行一个分组,然后对每组进行一个插入排序
例如递增序列 h = 3*h + 1;那么
先找到最大的递增序列 h = n/3
接着我们就可以对
0 0+h 0+2*h …. 0 + k*h
1 1+h 1+2*h …. 1 + k*h
…..
i i+h i+2*h …
等进行插入排序,当h=1时,排序就完成了
相关算法见下:
@Override
public void sort(Comparable[] array) {
int length = array.length;
int h = 0;
/**
* 递增序列 3*k + 1
*/
while (h < length / 3){
h = 3 * h + 1;
}
//间隔为n的插入排序
while (h >= 1){
for (int i = h; i < length ; i++){
for (int j = i ;j >= h ; j -= h){
//将a[i] a[i-h] a[i -2h] 做插入排序
if (less(array[j],array[j-h])){
exch(array,j,j-h);
}
}
}
h = h/3;
}
}
可以看到,希尔排序就是对间隔为h数进行插入排序。
3 归并排序
归并排序采用了一种分治和递归思想,它是将一个数组分为两部分,然后分别对两部分进行排序,然后将两个有序的数组进行归并成一个数组的过程。对两个数组的排序又可以采用递归的思想,分别进行拆分成两个数组,进行排序。然后再进行归并。
归并排序算法如下:自顶向下
/**
* @author Created by qiyei2015 on 2018/3/19.
* @version: 1.0
* @email: [email protected]
* @description: 归并排序
*/
public class MergeSort extends BaseSort{
private Comparable[] aux;
private static final int M = 15;
@Override
public void sort(Comparable[] array) {
aux = new Comparable[array.length];
int lo = 0;
int hi = array.length - 1;
sort(array,lo,hi);
}
/**
* 归并排序
* @param array
* @param lo
* @param hi
*/
private void sort(Comparable[]array,int lo,int hi){
if (hi - lo <0){
return;
}
int mid = lo + (hi - lo)/2;
//排左半边
sort(array,lo,mid);
//排右半边
sort(array,mid + 1,hi);
//归并
merge(array,lo,mid,hi);
}
/**
* 数组合并
* @param array
* @param lo
* @param mid
* @param hi
*/
private void merge(Comparable[] array,int lo,int mid,int hi){
int i = lo;
int j = mid + 1;
//将数组array复制到aux中
for (int k = lo ;k <= hi ; k++){
aux[k] = array[k];
}
//aux[lo..mid] aux[mid+1..hi]
for (int k = lo ; k <= hi ; k++){
if (i > mid){
//i 超过mid,说明左半边用完,取右半边
array[k] = aux[j++];
}else if (j > hi){
//j 超过hi,说明右半边用完,取左半边
array[k] = aux[i++];
}else if (less(aux[i],aux[j])){
//i 比j小,取i
array[k] = aux[i++];
}else {
array[k] = aux[j++];
}
}
}
}
可以看到,归并排序的核心过程就是这个归并的过程。其思想是用一个临时数组来存取元素。然后分别用两个指针来计数两个分段数组。比较这两个数组,将较小的赋值到数组中。这样就完成了归并过程
优化点:
1 在hi – lo <= M M为15等,也就是分割到一个大小差不多为16个数组的时候,可以考虑采用插入排序
2 在分割之后,如果本身数组已经有序的情况,例如a[mid] <= mid[mid+1]时,就不用归并了,因此优化如下:
* 归并排序
* @param array
* @param lo
* @param hi
*/
private void sort(Comparable[]array,int lo,int hi){
if (hi - lo <= M){
new InsertionSort().sort(array,lo,hi);
return;
}
int mid = lo + (hi - lo)/2;
//排左半边
sort(array,lo,mid);
//排右半边
sort(array,mid + 1,hi);
//归并
if (array[mid].compareTo(array[mid + 1]) > 0){
merge(array,lo,mid,hi);
}
}
对于链表等,可以考虑采用自底向上来进行归并。
另外,堆排序是一种稳定的排序
4 快速排序
快速排序是一种非常经典和常用的排序,被誉为20世纪最伟大的排序。快速排序的思想和归并类似,将数组进行一个切分。这样数组就分为三部分了
a[0..v-1] a[v] a[v+1…]使其a[v]之前的数小于a[v] a[v]之后的数大于a[v]。然后对剩余的数继续进行快速排序,快速排序也用到了分治和递归的思想。
快速排序的实现如下:
/**
* @author Created by qiyei2015 on 2018/3/25.
* @version: 1.0
* @email: [email protected]
* @description:
*/
public class QuickSort extends BaseSort {
@Override
public void sort(Comparable[] array) {
int lo = 0;
int hi = array.length - 1;
sort(array,lo,hi);
}
/**
* 快速排序,分治 递归
* @param array
* @param lo
* @param hi
*/
private void sort(Comparable[] array,int lo,int hi){
//递归结束条件
if (hi - lo <0){
return;
}
//parttion 已经处于该位置上的有序了,因此该位置上的数不用排序
int parttion = parttion(array,lo,hi);
sort(array,lo,parttion -1);
sort(array,parttion + 1,hi);
}
/**
* 找到切分点,将数组分为 a[lo,j-1] a[j] a[j+1,hi]三部分,其中a[lo..j-1] < a[j],a[j+1..hi] > a[j]
* @param array
* @param lo
* @param hi
* @return
*/
private int parttion(Comparable[] array, int lo, int hi){
Comparable v = array[lo];
int j = lo;
//找到切分点 a[lo..j-1] < a[j],a[j+1..hi] > a[j]
for (int i = lo + 1; i <= hi ;i++){
//如果a[i]比v小,就交换j+1和i,并且j++
if (less(array[i],v)){
exch(array,j + 1,i);
j++;
}
}
exch(array,lo,j);
return j;
}
快速排序的关键在于切分,切分就是在v的位置a[v]已经排好序。切分思想如下:
以第一个元素a[lo] v为例,将该元素排好序,从左lo + 1到右扫描数组,如果a[i]比v小,就交换a[i]与j+1。并且j++。这样比v小的数就从lo + 1到j了。而大于等于v的数就排到a[j]及其后面去了。最后一个小于v的元素是a[j]然后将其与a[lo]交换,这样a[lo…j-1]的元素就小于a[j]而其后的元素就大于等于a[j]了。
优化点:
1 切分以后的元素可以考虑采用插入排序
2 对于切分元素a[lo]进行随机化。
3 对于有序的数组可以采用双路排序
4 对于大量范围很小的数据采用三路快速排序
5 交换采用赋值操作,减少交换的次数
扩展
怎么在N个数中找到第M个大小的数?
可以利用切分的思想。
5 堆排序
由于堆的性质,根节点的数总是大于(小于)子结点,我们以最大堆为例,堆排序就是我们将数组从后往前,依次取堆的最大的元素(根节点),然后不断的调整堆的结构。最后将数组遍历完毕时,也完成了排序。由于调整堆结构的复杂度为O(logn),因此堆排序的时间复杂度也是O(nlogn)。
/**
* 堆排序
* @param array
*/
@Override
public void sort(Comparable[] array) {
MaxPQ<Integer> maxPQ = new MaxPQ(array.length);
for (int i = 0 ; i < array.length ; i++){
maxPQ.insert((Integer) array[i]);
}
for (int i = array.length - 1 ; i >= 0 ; i--){
array[i] = maxPQ.delMax();
}
}
其中MaxPQ是一个最大堆
以上就是一些高级排序算法,除了希尔排序算法的时间复杂度不好评估外(但是也小于O(n^2))。其他排序算法的平均时间复杂度都是O(nlogn)