本章介紹兩種高級排序,希爾排序和快速排序,這兩種排序比之前講到的簡單排序都要快很多;希爾排序大約需要O(N*(logN)2)的時間,快速排序的時間復雜度為(N*logN),這兩種算法和我們在講遞歸的時候講到的歸並排序不同,不需要大量的輔助存儲空間,快速排序是所有通用排序算法中最快的排序算法。
希爾排序:
希爾排序是基於插入排序的,希爾排序在插入排序的基礎之上通過加大插入排序元素之間的間隔,並在這些間隔元素之間進行插入排序,使數據實現大跨度的移動,從而使排序更有效率,我們習慣將排序時數據項之間的間隔稱為增量,用h來表示,下圖表示了十個數據項,以增量為4進行第一趟排序后的結果
通過上面的一趟排序之后我們可以看到元素離他們的最終有序序列位置都很近了,希爾排序就是通過創建這種交錯有序的數據項集合,從而提高排序的效率,當上面完成了4增量的排序之后,就可以進行普通的插入排序,這樣就比普通的插入排序的效率要高很多。
上面對於有十個數據項的數組初始增量我們設置為4,對於數據項更大的數組我們的初始增量也應該設置得更大,這里我們使用Knuth提出的間隔序列,序列的通項表達式為h=3*h+1,假設我們數據項的個數為100,那么通過Knuth間隔序列1,4,13,40,121,364,1093這里1093大於了我們的數據項1000,所以我們取初始的增量為364然后通過Knuth間隔序列依次減小我們的增量值,最終達到我們想要的一個個排序的效果,接下來我們給出希爾排序的java代碼
public class ArrayShell { public long[] theArray; private int nElems; public ArrayShell(int size) { this.theArray = new long[size]; this.nElems = 0; } public void insert(long value) { theArray[nElems++] = value; } public void shellSort() { //首先找到初始增量 int h = 1; int outer, inner; while (h <= nElems/3) { h = 3 * h + 1; } while (h > 0) { //以下就是普通的插入排序,只是步長換為h即可 for (outer = h; outer < nElems; outer++) { long temp = theArray[outer]; //增量為h,所以我們首個比較的元素從h開始,h和第一個索引為0的元素比較大小、h+1和索引為1比較是否交換。然后以此類推 inner = outer; while (inner > h - 1 && temp < theArray[inner - h]) { //需要進行數據交換的元素的滿足條件 theArray[inner] = theArray[inner - h]; inner -= h; } theArray[inner] = temp; } //從最大增量一直遞減到1做插入排序 h = (h - 1) / 3; } } }
希爾排序中間隔序列互質很重要,他能是每一趟排序更有可能保持前一趟已排序好的效果。
快速排序
快速排序是基於划分算法之上的一種排序算法,首先我們介紹一下划分算法的基本原理
- 划分
划分的基本原理就是把數據分為兩組,使關鍵值大於特定值的數據在一組,使關鍵值小於特定值的數據在另一組,比如我們日常生活中將家距離辦公點15km以內和以外的雇員分為兩組。划分算法中我們將兩個標記分別指向數組的兩頭,左邊的標記leftPtr ,向右移動,右邊的標記 rightPtr,向左移動,當leftPtr遇到比樞紐小的數據項時,繼續向右移動,當遇到比樞紐大的數據項時就停下來,同樣當rightPtr遇到比樞紐大的數值的時候繼續向左移動,遇到比樞紐小的就停下來,然后需要交換這兩個數據項。交換之后繼續移動指針,重復上面的步驟,知道兩個標記的值相等的時候則划分算法完成。
接下來我們看划分算法的java代碼
class ArrayPar { public Long[] theArray; private int nElems; public ArrayPar(int max) { theArray = new Long[max]; this.nElems = 0; } public void insert(Long value) { theArray[nElems] = value; nElems++; } public int partitionIt(int leftPtr, int rightPtr, long pivot) { while (true) { //當leftPtr遇到比樞紐小的數據項時,繼續向右移動(即 leftPtr++),當遇到比樞紐大的數據項時就停下來 while (leftPtr < rightPtr && theArray[leftPtr] <= pivot) //防止索引越界加上leftPtr<rightPtr的判斷 leftPtr++; //當rightPtr遇到比樞紐大的數據項時,繼續向左移動(即 rightPtr--),當遇到比樞紐大的數據項時就停下來 while (rightPtr > leftPtr && theArray[rightPtr] >= pivot) rightPtr--; //當leftPtr標記大於等於right標記的時候結束外層循環,否則交換兩個標記的數據項 if (leftPtr >= rightPtr) break; else swap(leftPtr, rightPtr); } return leftPtr; } /**交換數據方法*/ public void swap(int dex1, int dex2) { long temp = theArray[dex1]; theArray[dex1] = theArray[dex2]; theArray[dex2] = temp; } }
- 快速排序
快速排序的執行時間為O(N*logN)級,快速排序是基於划分算法之上的,利用遞歸的思想的一種排序算法,這里我們選擇數組的最右邊的元素作為樞紐,在划分算法完成之后,需要在之前的算法的基礎上加一步,將樞紐項和右數組的起始項進行位置交換,交換后的樞紐值的順序就是最終的順序,然后在利用遞歸將划分后的左右數組進行上述步驟。首先我們來看看快速排序的java代碼
class ArrayPar { public Long[] theArray; private int nElems; public ArrayPar(int max) { theArray = new Long[max]; this.nElems = 0; } public void insert(Long value) { theArray[nElems] = value; nElems++; } public int partitionIt(int leftPtr, int rightPtr, long pivot) { int right = rightPtr; while (true) { //當leftPtr遇到比樞紐小的數據項時,繼續向右移動(即 leftPtr++),當遇到比樞紐大的數據項時就停下來 while (leftPtr < rightPtr && theArray[leftPtr] <= pivot) //防止索引越界加上leftPtr<rightPtr的判斷 leftPtr++; //當rightPtr遇到比樞紐大的數據項時,繼續向左移動(即 rightPtr--),當遇到比樞紐大的數據項時就停下來 while (rightPtr > leftPtr && theArray[rightPtr] >= pivot) rightPtr--; //當leftPtr標記大於等於right標記的時候結束外層循環,否則交換兩個標記的數據項 if (leftPtr >= rightPtr) break; else swap(leftPtr, rightPtr); } swap(leftPtr,right); //最后將當前樞紐數值放入對應的排序位置 return leftPtr; } /** * 交換數據方法 */ public void swap(int dex1, int dex2) { long temp = theArray[dex1]; theArray[dex1] = theArray[dex2]; theArray[dex2] = temp; } /***快速排序的方法*/ public void recQuickSort(int left, int right) { if (right - left <= 0) //這里是遞歸的基值條件,當只有一個數據項的時候結束遞歸 return; else { long pivot = theArray[right]; //選擇最右邊的數據作為划分的樞紐數據 int partition = partitionIt(left, right, pivot); //調用划分的算法 //然后將划分好的兩部分利用遞歸的思想進行再次划分排序 recQuickSort(left, partition - 1); recQuickSort(partition + 1, right); } } }
下圖顯示了快速排序的過程
上面就是希爾排序算法和快速排序算法的所有內容