數據結構與算法之--高級排序:shell排序和快速排序


  高級排序比簡單排序要快的多,簡單排序的時間復雜度是O(N^2),希爾(shell)排序大約是O(N*(logN)^2),而快速排序是O(N*logN)。

說明:下面以int數組的從小到大排序為例。

 

希爾(shell)排序

  希爾排序是基於插入排序的,首先回顧一下插入排序,假設插入是從左向右執行的,待插入元素的左邊是有序的,且假如待插入元素比左邊的都小,就需要挪動左邊的所有元素,如下圖所示:

==>

圖1和圖2:插入右邊的temp柱需要outer標記位左邊的五個柱子都向右挪動

 

如圖3所示,相比插入排序,希爾排序是這樣做的:對固定間隔的元素做插入排序,然后減小間隔,重復做插入排序,直到間隔減小為1。

 ==> 

圖3和圖4: outer位置和inner-h位置的柱子做插入排序 

數據量大的圖形看這個過程更容易形象地把握算法特點,如圖5和6,總元素數量等於100:

  

圖5和圖6:間隔分別為40和13執行完插入排序后的效果

相比簡單插入排序,大間隔地做插入排序有兩個好處:

  一、大間隔直接導致需要挪動的數據稀少,且數據挪動的效率高,圖5中一次挪動可以跨越40個位置;

  二、經過前一步大間隔的插入排序后,整個數組從整體上粗略地看已經有了明顯的順序,后一步小間隔的插入排序時,一部分操作是不需要挪動數據的,再次減少了挪動數據的次數。

間隔的序列:間隔的常用序列,通過遞歸表示:h=3*h+1。(1,4,13,40,121 ...)

希爾排序的效率:“沒有理論上分析希爾排序的效率的結論,各種基於實驗的評估,估計它的時間級從O(N^(3/2))到O(N^(7/6))”--[1]。

參考代碼:

 

 1     /**
 2      * Shell排序可以說是插入排序的升級版,相比較插入排序,
 3      * Shell排序首先使用大間隔、少元素的插入排序,使得每次移動的數據少,但是移動的跨度大;
 4      * 然后再使用小間隔,略多數量元素的插入排序,經過上一步插入排序,很多數據已經距離排序位置不遠了,
 5      * 這樣第二次插入排序時,需要移動的數據的數量就會減少,
 6      * 間隔最終減小到1.
 7      */
 8     public static int[] shellSort(int[] input) {
 9         int length = input.length;
10         int inner, outer;
11         int temp;
12 
13         int h = 1;
14         while (h <= length / 3) {
15             h = h * 3 + 1;
16         }
17 
18         while (h > 0) {
19             for (outer = h; outer < length; outer++) {
20                 temp = input[outer];
21                 inner = outer;
22 
23                 while (inner > h - 1 && input[inner - h] >= temp) {
24                     input[inner] = input[inner - h];
25                     inner -= h;
26                 }
27                 input[inner] = temp;
28             }
29             h = (h - 1) / 3;
30         }
31 
32         return input;
33     }

 

快速排序

  快速排序算法的策略是這樣的:首先把數組用某個值分為兩個子數組,且稱這個值為分組值,一個子數組中的元素均小於分組值,另一子數組則均大於等於分組值,這里的子組內並不排序;然后,再分別對兩個子組進行再分組,重復遞歸這個過程,直到最后每兩個元素作為一組進行再分組,整個數組就排好序了。

  分組過程具體如下:同時從左往右和從右往左掃描數組,記掃描標記位為LP和RP。在LP一邊,若發現元素小於分組值則跳過(即向右移動一位檢查下一個元素),否則等待RP的掃描;RP若發現元素大於等於分組值跳過,直到找到小於分組值的元素,然后LP和RP位置的元素交換,重復這個過程,直到LP和RP相遇。

  如圖7,8所示,以11號元素為分組值,LP停在0號位置,RP跳過10號,停在圖7中的9號位置(粉色柱),然后0號和9號交換,后續重復這個過程。

 => 

圖7和圖8:以11號柱作為分組值,0號和9號柱子將交換

圖9:100個元素的數組經過兩次分組后的效果

  分組值的選擇,可以想見,理想的分組值應該是待分組元素的中值,這樣分組后子組在數量少幾乎是一半對一半,不過找中值無疑增加了算法的工作量。圖7中采用了更簡單的方式,直接選數組最右邊的元素為分組值,分組結束后,再把這個值交換到LP和RP相遇的位置,假如初始數組是從大到小排序的,這種情況下,選擇最右邊的元素作為分組值,其區分度就很差了。更好用的方法是所謂的取首尾中三項數據的中值或者平均值。

  通過對算法過程的描述可知,其時間復雜度應該為:O(N*logN),比簡單排序和希爾排序都要快

參考程序如下:

 1     /**
 2      * Time Complexity: O(N*logN)
 3      */
 4     public static void quickSort(int[] input, final int left, final int right) {
 5         if (left < right) {
 6             int partition = partition(input, left, right);
 7             quickSort(input, left, partition - 1);
 8             quickSort(input, partition, right);
 9         }
10     }
11 
12 
13     public static int median(int i1, int i2, int i3) {
14         int[] arrTmp = {i1, i2, i3};
15         int min = arrTmp[0];
16         int max = arrTmp[0];
17         for (int i = 1; i < arrTmp.length; i++) {
18             min = min < arrTmp[i] ? min : arrTmp[i];
19             max = max > arrTmp[i] ? max : arrTmp[i];
20         }
21         return (i1 + i2 + i3) - min - max;
22     }
23 
24     public static int partition(int[] input, final int left, final int right) {
25         final int mid = (left + right) / 2;
26         int pivotValue = median(input[left], input[mid], input[right]);
27 
28         int leftPtr = left;
29         int rightPtr = right;
30         while (leftPtr < rightPtr) {
31             if (input[leftPtr] < pivotValue) {
32                 leftPtr++;
33                 continue;
34             }
35             if (input[rightPtr] > pivotValue) {
36                 rightPtr--;
37                 continue;
38             }
39             int temp = input[leftPtr];
40             input[leftPtr] = input[rightPtr];
41             input[rightPtr] = temp;
42             leftPtr++;
43             rightPtr--;
44         }
45 
46         // 找出大於pivotalValue的第一個值的位置。
47         if (input[rightPtr] > pivotValue) {
48             return rightPtr;
49         } else {
50             return rightPtr + 1;
51         }
52     }

 

 

參考文獻

【1】Java數據結構與算法 Rober Lafore 2nd

說明: 文中的圖片來自文獻【1】附帶的Java applet演示小程序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM