原博主:https://www.jianshu.com/p/d730ae586cf3
1,希爾排序概述
希爾排序(shell Sort) 是插入排序的一種算法,是對直接插入排序的幾個優化,也稱縮小增量排序。
注意:1,希爾排序是非穩定性排序算法;
2,為了方便記憶算法,我習慣將其記作 “三層for循環+if” ------** for(for(for(if)))**);
2,比較直接插入排序算法:
希爾排序是基於直接插入排序的以下兩點性質而提出的改進方法:
1,插入排序在對幾乎已經排好序的數據操作時,效率高,即可達到線性排序的效率。
2,插入排序一般是低效的,因為插入排序每次只能將數據移動一位。
3,希爾排序的思想
希爾排序是將待排序的數組元素 按下標的一定增量分組,分成多個子序列,然后對多個子序列進行直接插入排序算法排序;
然后依次縮減增量在進行排序,直到增量為1時,進行最后一次直接插入排序,排序結束。
理解過程:
**增量d 的范圍: **1<= d < 待排序數組的長度 (d 需為 int 值)
**增量的取值: **一般的初次取序列(數組)的一半為增量,以后每次減半,直到增量為1。
第一個增量=數組的長度/2,
第二個增量= 第一個增量/2,
第三個增量=第二個增量/2,
以此類推,最后一個增量=1。
好的增量序列的共同特征:
① 最后一個增量必須為1;
② 應該盡量避免序列中的值(尤其是相鄰的值)互為倍數的情況。
三、原理過程圖解
四、時間復雜度
**希爾排序的執行時間依賴於增量序列。 **
希爾排序耗時的操作有:比較 + 后移賦值。
時間復雜度情況如下:(n指待排序序列長度)
1) 最好情況:序列是正序排列,在這種情況下,需要進行的比較操作需(n-1)次。后移賦值操作為0次。即O(n)
2) 最壞情況:O(nlog2n)。
3) 漸進時間復雜度(平均時間復雜度):O(nlog2n)
白話理解:
希爾排序是按照不同步長對元素進行插入排序,當剛開始元素很無序的時候,步長最大,所以插入排序的元素個數很少,速度很快;當元素基本有序了,步長很小,插入排序對於有序的序列效率很高。所以,希爾排序的時間復雜度會比O(n²)好一些。
希爾算法的性能與所選取的增量(分組長度)序列有很大關系。只對特定的待排序記錄序列,可以准確地估算比較次數和移動次數。想要弄清比較次數和記錄移動次數與增量選擇之間的關系,並給出完整的數學分析,至今仍然是數學難題。希爾算法在最壞的情況下和平均情況下執行效率相差不是很多,與此同時快速排序在最壞的情況下執行的效率會非常差。希爾排序沒有快速排序算法快,因此中等大小規模表現良好,對規模非常大的數據排序不是最優選擇。
(注:專家們提倡,幾乎任何排序工作在開始時都可以用希爾排序,若在實際使用中證明它不夠快,再改成快速排序這樣更高級的排序算法。)
五,穩定性分析:
由於多次插入排序,我們知道一次插入排序是穩定的,不會改變相同元素的相對順序,但在不同的插入排序過程中,相同的元素可能在各自的插入排序中移動,最后其穩定性就會被打亂
所以希爾排序是不穩定的。
六,核心代碼(升序排序為例)。
(注:為方便記憶算法,我習慣將其記作“三層for循環+if ” ------for(for(for(if))))
//希爾排序 升序 for (int d = arr.length / 2;d>0;d /= 2){ //d:增量 7 3 1 for (int i = d; i < arr.length; i++){ //【插入排序 可以提取出來】 //i:代表即將插入的元素角標,作為每一組比較數據的最后一個元素角標 //j:代表與i同一組的數組元素角標(前一個元素角標) for (int j = i-d; j>=0; j-=d){ //在此處-d 為了避免下面數組角標越界 j=j-d if (arr[j] > arr[j + d]) {// j+d 代表即將插入的元素所在的角標 //符合條件,插入元素(交換位置) int temp = arr[j]; arr[j] = arr[j + d]; arr[j + d] = temp; } } //==================================== } } return arr; }
測試所有源碼:
public class ShellSorts { public static void main(String[] args) { // 聲明一個數組 int[] arr = new int[] { 3, 2, 5, 4, 1 }; System.out.println("原數組:" + Arrays.toString(arr)); System.out.println("排序后" + Arrays.toString(insertionSort(arr))); } /** * 希爾排序思路 * 1,先判斷數組為空或者只有一個元素的情況 * 2,確定截取增量長度=數組長度/2(循環) 直到增量為1 * 3,對每一個增量進行排序【一般采用直接插入排序】 * 4,接着遍歷下一層增量,直到增量為1,排序完成 * @param arr * @return */ // 簡化希爾排序 public static int[] insertionSort(int[] arr) { // 數組長度小於等於1的情況 if (arr == null || arr.length <= 1) { return arr; } // 希爾排序 升序 for (int d = arr.length / 2; d > 0; d /= 2) { // d:增量 7 3 1 for (int i = d; i < arr.length; i++) { // 【插入排序 可以提取出來】 // i:代表即將插入的元素角標,作為每一組比較數據的最后一個元素角標 // j:代表與i同一組的數組元素角標(前一個元素角標) for (int j = i - d; j >= 0; j -= d) { // 在此處-d 為了避免下面數組角標越界 // j=j-d if (arr[j] > arr[j + d]) {// j+d 代表即將插入的元素所在的角標 // 符合條件,插入元素(交換位置) int temp = arr[j]; arr[j] = arr[j + d]; arr[j + d] = temp; } } } } return arr; } // 算法提煉 兩元素位置交換方法 public static void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; } // 對於java自帶的Arrays.toString()方法的實現 public static void printArray(int[] arr) { System.out.print("["); for (int x = 0; x < arr.length; x++) { if (x != arr.length - 1) { System.out.print(arr[x] + ", "); } else { System.out.println(arr[x] + "]"); } } } }