希爾排序java
希爾排序是計算機科學家Donald L.Shell 而得名,他在1959年發現了希爾排序算法。希爾排序基於插入排序,可是增長了一個新的特性,大大提升了插入排序的執行效率。算法
插入排序:複製的次數太多
shell
因爲希爾排序是基於插入排序的,因此須要回顧下「插入排除」。在插入排除執行的一半的時候,標記符左邊這部分數據項都是排過序的(這些數據之間是有序的),而記右邊的數據項沒有排過序。這個算法取出標記符所指的數據項,把它存儲在一個臨時的變量。接着,從剛剛被移除的數據項的左邊第一個單元看是,每次把有序的數據項向右移動一個單元,直到存儲在臨時變量裏的數據項可以有序回插。
數組
下面是插入排序帶來的問題。假設一個很小的數據項在很靠近右端的位置上,這裏原本應該是值比較大的數據項所在的位置。把這個小數據項移動到在左邊的正確位置上,全部的中間數據項(這個數據項原來所在的位置和它應該移動到的位置之間的數據項)都必須向右移動一位。這個步驟對每個數據項都執行了將近N次的複製。雖不是全部數據項都必須移動N個位置可是數據項平均移動了N/2個位置,這就執行了N次N/2個移位,總共是N²/2次複製。所以,插入排序的執行效率是O(N²)。
dom
若是能以某種方式沒必要一個一個地移動全部中間的數據項,就能把較小的數據項移動到左邊,那麼這個算法的執行效率就會有很大的改進。
spa
n-增量排序code
希爾排序經過加大插入排序中元素之間的間隔,並在這些有間隔的元素中進行插入排序,從而是數據項能大跨度地移動。當這些數據項排過一趟序後,希爾排序算法減少數據項的間隔在進行排序,依次進行下去。進行這些排序時數據項之間的間隔被稱爲增量,而且習慣上用字母h來表示。圖7.1顯示了增量爲4時對包含10個數據項的數組進行排序的第一個步驟的狀況。在0、4和8號位置上的數據項已經有序了。
排序
圖 7.1 4-增量排序0、四、和8號數據項
遞歸
當對0、4和8號數據項完成排序以後,算法向右移一步,對一、5和9號數據項進行排序。這個排序過程持續進行,直到全部的數據項都已經完成了4-增量排序,也就是說全部間隔爲4的數據項之間都已經排列有序。這個過程如圖7.2所示(使用更爲簡潔形象的圖例表示)。
class
在完成以4位增量的希爾排序以後,數組能夠當作是由4個子數組組成:(0、四、8),(一、五、9),(二、6)和(三、7),這四個子數組內分別是徹底有序的。這些子數組相互交錯着排列,然而彼此獨立。
圖7.2 完整的以4爲增量的一趟排序
注意,在這個例子中,在完成以4爲增量的希爾排序後,素有元素離它在最終有序序列中的位置相差都不到兩個單元。這就是數組「基本有序」的含義,也正是希爾排序的奧祕所在。經過建立這種交錯的內部有序的數據項集合,把完成排序所必需的工做量降到了最小。
插入排序對基本有序的數組排序是很是有效的。若是插入排序只須要把數據項移動一位或者兩位,那麼算法大概須要O(N)時間。這樣,當數組完成4-增量排序以後,能夠進行普通的插入排序,即1-增量排序。4-增量排序和1-增量排序結合起來應用,比前面不執行4-增量排序而僅僅應用普通的插入排序要快得多。
減少間隔
上面已經演示了以4爲初始間隔對包含10個數據項的數組進行排序的狀況。對於更大的數組,開始的間隔也應該更大。而後間隔不斷減少,直到間隔變成1。
舉例來講,含有1000個數據項的數組可能先以364爲增量,而後以121爲增量,以40爲增量,以13爲增量,以4爲增量,最後以1爲增量進行希爾排序。用來造成間隔的數列(在本例中爲364,121,40,13,4,1)被稱爲間隔序列。這裏所表示的間隔序列由Knuth提出,此序列是很經常使用的。數列以逆向的形式從1開始,經過遞歸表示
h = 3*h+1
來產生,初始值爲1。表7.1的前兩欄顯示了這個公式產生的序列。
表7.1 Knuth間隔序列
還有一些其餘的方法也能產生間隔序列;後面會講到這個問題。首先,來研究使用Knuth序列進行希爾排序的狀況。
在排序算法中,首先在一個短小的循環中使用序列的生成公式計算出最初的間隔。h值最初被賦爲1,而後應用公式h=3*h+1生成序列,一、四、1三、40、12一、364,等等。當間隔大於數組大小的時候這個過程中止。對於一個含有1000個數據項的數組,序列的第七個數字,1093就太大了。所以,使用序列的第六個數字做爲最大的數字來開始這個排序過程,做364-增量排序。而後,每完成一次排序例程的外部循環,用前面提供的此公式的倒推式來減少間隔:
h = (h-3)/3
它在表7.1的第三欄中顯示。這個倒推的公式生成逆置的序列 36四、12一、40、1三、四、1從364開始,以每個數字做爲增量進行排序。當數組用1-增量排序後,算法結束。
希爾排序的Java代碼
package com.goaji.shellsort; public class ArraySh{ private long[] theArray; private int nElems; public ArraySh(int max){ theArray = new long[max]; nElems = 0; } public void insert(long value){ theArray[nElems] = value; nElems++; } public void display(){ System.out.print("A="); for (int i = 0; i < nElems; i++) { System.out.print(theArray[i] + " "); } System.out.println(""); } public void shellSort(){ int inner,outer; long temp; int h=1; while(h<=nElems/3) h = h*3+1; while(h>0){ for (outer = 0; outer < nElems; outer++) { temp = theArray[outer]; inner = outer; while(inner>h-1 && theArray[inner-h]>=temp){ theArray[inner] = theArray[inner-h]; inner-=h; } theArray[inner] = temp; } h = (h-1)/3; } } }
public static void main(String[] args) { int maxSize = 10; ArraySh arr; arr = new ArraySh(maxSize); for (int i = 0; i < maxSize; i++) { long n = (int)(java.lang.Math.random()*99); arr.insert(n); } arr.display(); arr.shellSort(); arr.display(); } //輸出: A=26 87 14 50 14 9 53 37 50 83 A=9 14 14 26 37 50 50 53 83 87
能夠是maxSize取更大的值,可是也不要太大。對10000個數據項須要將近1分鐘的時間來完成排序;
儘管希爾排序的算法只須要幾行代碼來實現,可是跟蹤這個算法也不是很簡單的。
其餘間隔序列
選擇間隔序列能夠稱得上是一種魔法。這裏只討論了公式h=h*3+1生成間隔序列,可是應用其餘間隔序列也取得了不一樣程度的成功。只有一個絕對的條件,就是逐漸減少的間隔最後必定要等於一,所以最後一趟排序是一次普通的插入排序。
希爾排序的效率
迄今爲止,除了在一些特殊的狀況下,尚未人可以從理論上分析希爾排序的效率。有各類各樣基於試驗的評估,估計它的時間級從O(N³/²)到O(N的7/6次方)。