排序演算法(2)
排序演算法1中,對於選擇排序和插入排序進行了介紹,通過程式碼,可以看出,假設有一個給定的亂序陣列,插入排序因為都只能通過交換相鄰的元素,於是得到的時間複雜度是相對來說比較高的。從這也可以看出,在插入排序中,元素只能緩慢的從一端到另一端。從這就可以觀察得到,這應該還有優化的空間,於是,基於插入排序的改進演算法進一步提出。
希爾排序
既然插入排序對於亂序物件的處理能力比較差,也就是某個物件需要從一端到另外一端。基於這個思想,能不能不讓物件只一個一個相鄰交換。因此,希爾排序通過擴大每個物件的間隔交換值h,從而保證任意間隔值h都都是有序的;這樣通過較大的將間隔值,從而保證物件不是一個一個相鄰移動,從而提高效率。看程式碼,此程式碼還是基於排序演算法1中的模板,以下只展示sort部分,其餘部分可參見排序演算法1:
//目標:實現a[]按照升序排列
public static void sort(Comparable[] a){
int N = a.length;
//資料間隔h時為有序。
int h = 1;
//找到最大的間隔h,除以幾可以根據需要進行調節
while(h < N/3){
h = 3*h + 1;
}
while(h >= 1){
for (int i = h; i < N ; i++) {
for (int j = i; j >= h && less(a[j],a[j-h]); j-=h) {
exch(a,j,j-h);
}
}
show(a);
h = h/3;
}
}
在此,可能體會不到希爾排序起到了什麼作用,通過一個簡單的小實驗,體會下
希爾排序結果
原始陣列:20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
第一次:7 6 5 4 3 2 1 13 12 11 10 9 8 20 19 18 17 16 15 14
第二次:3 2 1 4 7 6 5 9 8 11 10 13 12 16 15 14 17 20 19 18
第三次:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
通過觀察可以得到,希爾排序很快就可以把亂序偏後的元素移動到另外一邊,這就是希爾排序的強大之處。所以說,在此也會有很多疑問,如何進行選擇間隔遞增的h值呢。這個在很多論文都進行了研究,但是是一個比較難的問題,實際中,可以通過調節h值進行測試。也可以看出,希爾排序解決了插入排序在亂序情況下移動困難的問題。並且實踐也證明,希爾排序對於中等大小的陣列的執行時間是 可以接受的,並且也不需要額外的記憶體空間。
歸併排序
通過希爾排序,可以知道,其思想是通過控制全部物件中間隔長度為h的保持有序。但基於此是否還可以想到,能否通過一直保證部分有序,最終實現全域性有序呢。歸併排序通過這樣的思想。首先給的的物件陣列,一分為二,然後有兩個子陣列物件,通過保證兩個子陣列物件有序,然後將結果進行歸併,從而達到全域性有序。也就是說,歸併排序通過分治的思想,最後達到全部物件有序。
其實想法是比較簡單的,但實現起來卻要考慮很多問題。比如說,對於一個很長的大陣列來說,如果每次歸併都要建立一個新陣列來進行儲存歸併結果,這導致很大的空間浪費,因此,在這可以考慮原地歸併的思路,也就是建立一個和原陣列大小一樣的陣列,進行每次的歸併儲存。歸併排序的程式碼如下
private static Comparable[] aux;
//將兩個已排序陣列物件進行歸併
public static void merge(Comparable[] a, int lo, int mid, int hi) {
int i = lo, j = mid + 1;
for (int k = 0; k <= hi; k++) {
aux[k] = a[k];
}
for (int k = 0; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (less(aux[j], aux[i])) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
//排序
public static void sort(Comparable[] a) {
aux = new Comparable[a.length];
//整個進行排序
sort(a, 0, a.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi) {
if(hi <= lo) return;//遞迴出口
//二分
int mid = lo + (hi-lo)/2;
sort(a, lo, mid);//保證左邊有序
sort(a, mid+1, hi);//保證右邊有序
merge(a, lo, mid, hi);//進行歸併
}
上述是一個自頂向下的歸併排序,也就是從整個陣列為開端,一步一步的分治,從而達到全部排序的目的。當然,也可以通過自下往上的方式達到目的,在此不再重複贅述。對於歸併排序的演算法複雜度,可以看見,歸併排序似乎有點二分的感覺,是的,每一個都把資料進行二分,因此時間複雜度相對於插入或者選擇肯定是降低了的,具體的時間複雜度不在詳細討論,後面會對各種演算法的時間空間複雜度進行一個詳細的論述。
總結