引言:
前面詳解了如何優化冒泡排序?,圖解選擇排序與插入排序,這些簡單排序算法平均時間復雜度都是O(n^2)。希爾排序是第一批打破二次時間屏障的算法之一。下面我們來分析為什么希爾排序可以打破二次時間復雜度。
一、分析簡單排序算法的下界
逆序:具有性質i < j但 a[i] > a[j]的序偶(a[i],a[j])。如序列34,8,64,51,32,21有9個逆序,即(34,8)、(34,32)、(34,31)、(64,51)、(64,32)、(64,21)、(51,32)、(51,21)以及(32,21)。注意,這正好是需要(隱含)執行的交換次數。事實總是這樣,因為交換兩個不按順序排序的相鄰元素恰好消除一個逆序,而一個排過序的數組沒有逆序。由於算法中還有O(N)量的工作,假設 I 是原數組中的逆序數,所以插入排序的運行時間是O(I + N)。所以,如果逆序數是O(N),則插入排序以線性時間運行;冒泡排序通過加標志位也可以在有序的條件下達到最優O(N)。
所以我們可以通過計算原始序列中的平均逆序數得出簡單排序的平均時間精確的界。
這里假設元素互異(如果允許重復,那我們連重復的平均次數都無法知道)。
定理1:N個互異數的數組的平均逆序數是N(N-1)/4。
證明:N個互異數的序列L與逆序列Lr中,所有序偶為N(N-1)/2(很容易理解:相當於從N個互異數中選兩個元素,這兩個元素有順序,即:A2N= N(N-1)/2)。那平均的逆序表列該有一半:即 N(N-1)/4個逆序。
定理2:通過交換相鄰元素進行排序的任何算法平均都需要
證明:因為初始的平均逆序數為N(N-1)/4,而每次交換只減少一個逆序數,因此需要
結論:這個下界告訴我們,為了使一個排序算法以亞二次或O(N^2)時間運行,必須執行一些比較,特別是要對相距較遠的元素進行交換。一個排序算法通過刪除逆序得以向前進行,而為了有效地進行它必須使每次交換刪除不止一個逆序。下面我們來看希爾排序怎么打破二次時間界。
二、希爾排序(ShellSort)
希爾排序按其設計者希爾(Donald Shell)的名字命名。希爾排序通過多次插入排序來實現。它通過比較相距一定間隔的元素來工作;各趟比較所用的距離隨着算法的進行而減小,直到只比較相鄰元素的最后一趟排序為止。所以希爾排序也叫縮減增量排序。
算法思想:使用一個序列h1,h2,….ht,叫做增量序列。只要h1=1,任何增量序列都是可行的。在使用增量hk排序后,對於每一個i 我們都有a[i]<=a[i+hk];所有相隔hk的元素都被排序,這稱為hk排序。只要最后h1=1(這時就是最普通的插入排序),希爾排序都可完成工作。一趟hk排序就是對hk個子數組進行插入排序。
排序過程
- 選擇一個增量序列h1,h2,….ht,h1=1。
- 根據增量序列個數,即循環t次進行排序,每次排序結束后更換為ht-1的增量。
- 把原數組分為ht個子數組,對每個子數組進行插入排序。
下面我們使用增量序列1、3、5對序列 {3, 7, 1, 13, 9, 11, 5, 8, 2, 4, 12, 6, 10}進行希爾排序的圖解。

每種顏色代表一個子數組,很直觀看到每一趟排序過程及結果。
- java實現冒泡排序:代碼中我們使用希爾建議的增量序列(但效率不高)。ht=N/2和hk=hk+1/2。理解了插入排序流程,代碼實現很簡單。
private static <T extends Comparable<? super T>> void shellsort(T[] a) {
int j;
T tmp = null;
for (int gap = a.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < a.length; i++) {
tmp = a[i];
for (j = i; j >= gap &&
tmp.compareTo(a[j - gap]) < 0; j -= gap) {
a[j] = a[j - gap];
}
a[j] = tmp;
}
}
}
- 時間、空間復雜度及穩定性分析:
- 希爾排序的運行時間依賴於增量序列的選擇,而證明很復雜【有興趣可查看其他資料】。
- 使用希爾增量時希爾排序最壞時間復雜度是:O(n^2)。
- 使用Hibbard增量的希爾排序最壞時間復雜度是:O(n3/2);最優時間復雜度是O(n5/4)。
- 使用Sedgewick 增量序列,排序最壞時間復雜度是:O(n4/3);平均時間復雜度是O(n7/6)。最好的序列是{1,5,19,41,109……}。該序列中的項或者是9 * 4i - 9 * 2i +1的形式,或者是4i - 3* 2i+1)的形式。
- 空間復雜度:只用到一個臨時變量,所以空間復雜度為O(1);
- 穩定性:不穩定排序。因為每一趟的步長不一樣,所以步長長的插入排序可能會把后面的元素插入到前面。
三、總結
本篇說明了想要打破二次時間界,必須比較相距較遠的元素來進行交換,這樣可以一次交換刪除多個逆序,以達到突破二次時間界。希爾排序通過使用增量序列來將原始序列分為多個子序列,對每個子序列進行插入排序。只要最終增量序列h1=1,希爾排序都可正常工作。希爾排序時間嚴重依賴於增量序列的選擇,我們可以直接先將好的增量序列“打表”存在數組中,這樣不用每次排序都去計算。
聲明:畫圖碼字都辛苦,如有轉載,請注明出處。文中引用來自《數據結構與算法java語言描述(原書第三版)》