排序算法有很多,今天我來講一講其中實用性最強的一種算法——快速排序。
爲什麼說它實用性最強呢?因爲快速排序的平均性能非常好,雖然它的最壞運行時間爲O(n^2),但是平均運行時間是O(nlgn),而且裏面隱含的常數因子很小。而且它是原地排序的,所謂原地排序,就是不需要開闢新的數組空間,就可以進行排序。
快速排序基於分治模式,所謂分治模式,就是把問題拆成一個個小問題,直到問題可以解決。以下是分治過程的三個步驟:
分解:數組A[p...r]被劃分成兩個子數組(可能空)A[p...q-1]和A[q+1...r],使得前者每個元素都小於等於A(q),後者中的每個元素都大於A(q)。
解決:通過遞歸調用快速排序,對子數組A[p...q-1]和A[q+1...r]排序。
合併:因爲兩個子數組是就地排序的,所以合併不需要操作。整個數組A[p...r]已排序。(這裏沒有合併過程,和合並排序有很大不同。關於合併排序)
快速排序代碼如下:
void QuickSort(int[]A, int p, int r){
if(p < r){
int q = Partition(A, p, r);
QuickSort(A, p, q - 1);
QuickSort(A, q + 1, r);
}
}
快速排序的核心是Partition過程,代碼如下:
/**對子數組A[p...r]進行就地重排
* @param A
* @param p
* @param r
* @return
*/
int Partition(int[] A, int p, int r){
int x = A[r];
int i = p - 1;
for(int j = p; j < r; j++){
if(A[j] <= x){
i++;
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
int temp = A[i + 1];
A[i + 1] = A[r];
A[r] = temp;
return i + 1;
}
下面的圖片顯示了Partition在一個包含了八個元素數組上的操作。
所以說,其實數組被分成了四個部分:
以上就是快速排序的內容。比較簡單,圖片來自於《算法導論》
但是!!但是其實有個坑
在做阿里筆試題的時候發現了。尼瑪它的快速排序怎麼感覺和我的不一樣。百度了一下,偶,果然不一樣。。下面這個排序的思路來自數據結構(嚴蔚敏),代碼如下:
public void qsort(int[] A, int left, int right){
if(left < right) {
int key = A[left];
int low = left, high = right;
while (low < high) {
while (low < high && A[high] > key) {
high--;
}
A[low] = A[high];
while (low < high && A[low] < key) {
low++;
}
A[high] = A[low];
}
A[low] = key;
qsort(A, left, low - 1);
qsort(A, low + 1, right);
}
}
這個思路的意思是:先選定左邊爲基準值(你要選右邊也可以,後面代碼相應改一改),定義low,high,然後先從後向前找,如果找到了比基準值小的,則low下標處等於該值,否則high--,然後再從前往後找,如果找到了比基準值大的,則high下標處等於該值,否則low++,直到low和high相等。這道題令人驚奇的是它交換數據的方式:
每個值被覆蓋之前。都被拿走了。首先是基準值,然後之後每個值都是先拿走,然後纔可能被覆蓋,最後再把基準值放到最後一個被拿走的值裏。這樣就實現了一波交換。牛逼啊。
如果對代碼有疑惑可以看我的github源碼,上面有所有方法的單元測試:https://github.com/qjkobe/IntroductionToAlgorithms
如果發現問題,請立刻告訴我。我可不想誤人子弟