高階排序演算法
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)