###1.原理/思路
快速排序一般基於遞歸實現。其思路是這樣的:
1.選定一個合適的值(理想情況中值最好,但實現中一般使用數組第一個值),稱爲“樞軸”(pivot)。
2.基於這個值,將數組分爲兩部分,較小的分在左邊,較大的分在右邊。
3.可以肯定,如此一輪下來,這個樞軸的位置一定在最終位置上。
4.對兩個子數組分別重複上述過程,直到每個數組只有一個元素。
5.排序完成。
###2.分析做法
1.附設兩個指針left和right,初始化時分別指向數組的下界和上界,設樞軸關鍵字爲pivotloc
(第一趟 left = 0 ,right = arr.length - 1)
2.從表的最右側位置,依次向左搜索找到第一個關鍵字小於pivotloc的記錄和樞軸記錄交換。具體操作:當left<right時,
如果arr[right]的值大於等於pivotloc,則向左移動指針right(–right);否則將arr[right]與樞軸記錄交換。
3.從表的最左側位置,依次向右搜索找到第一個關鍵字大於pivotloc的記錄和樞軸記錄交換。具體操作:當left<right時,
如果arr[left]的值小於等於pivotloc,則向右移動指針left(–left);否則將arr[left]與樞軸記錄交換。
4.重複步驟2 和3,直到left和right相等爲止,此時left或right的位置即爲樞軸在此排序中的最終位置,原表被分成兩個字表。
###3.代碼實現
#####1. 普通快速排序實現
public void quickSort(int[] arr, int l, int r) {
if (l >= r) return;
int p = partition1(arr, l, r);
quickSort1(arr, l, p - 1);
quickSort1(arr, p + 1, r);
}
//返回p 使得arr[l...p-1] < arr[p]; arr[p+1...r] > arr[p]
private int partition(int[] arr, int l, int r) {
int v = arr[l];
// arr[l+1,j] < v ; arr[j+1,i) > v
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i] < v) {
SortUtil.swap(arr, i, j + 1);
j++;
}
}
SortUtil.swap(arr, l, j);
return j;
}
#####2. 優化方式一
/*
* 將第一個元素爲基準值改爲隨機一個元素爲基準值
* 降低退化爲O(n^2)的概率
* 數學期望值爲O(nlogn)
* 這樣就不會受“幾乎有序的數組”的干擾了
* 但是對“幾乎亂序的數組”的排序性能可能會稍微下降
* 亂序時這個交換沒有意義
*/
//返回p 使得arr[l...p-1] < arr[p]; arr[p+1...r] > arr[p]
private int partition1(int[] arr, int l, int r) {
//產生一個 l 到 r 的隨機數作爲數組基準值下標
//將隨機數與數組第一個數交換,即讓隨機數作爲基準值
//減小近乎有序的概率
int ran = (int) (Math.random() * (r - l + 1) + l);
SortUtil.swap(arr,l,ran);
int v = arr[l];
// arr[l+1,j] < v ; arr[j+1,i) > v
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i] < v) {
SortUtil.swap(arr, i, j + 1);
j++;
}
}
SortUtil.swap(arr, l, j);
return j;
}
#####3. 優化方式二
左邊去找比基準值大的元素,右邊找比基準值小的元素,如果都找到了,就交換左右兩邊的元素,同時左邊的索引i++,右邊的索引j–,繼續查找下一個
/*
* 如果數組中存在大量重複元素,並且第一個數也是重複元素時
* 我們一般以第一個元素作爲基準值,這樣就有可能導致大量
* 可能導致兩邊子遞歸規模差距懸殊的情
* 時間複雜度就很有可能退化爲O(n^2)
*/
private void quickSort(int[] arr, int l, int r) {
if (l >= r) return;
int p = partition(arr, l, r);
quickSort(arr, l, p - 1);
quickSort(arr, p + 1, r);
}
private int partition(int[] arr,int l,int r){
int v = arr[l];
int i = l+1;
int j = r;
while (true){
//左邊開始掃描,直到找到一個比v大的數值,否則i++ i不能等於r 因爲後面i++就會數組越界
while (arr[i] < v && i < r)
i++;
//右邊開始掃描,直到找到一個比v小的數值,否則j--
while (arr[j] > v && j >= l)
j--;
if(i >= j) break;
SortUtil.swap(arr,i,j);
i++;
j--;
}
SortUtil.swap(arr,l,j);
return j;
}
#####4. 優化方式三(三路快排)
private void quickSort(int[] arr,int l,int r){
if(l >= r)
return;
int v = arr[l];
//變量定義要保證初始空間爲空
int lt = l; //arr[l+1...lt] < v;
int gt = r + 1; //arr[gt...r] > v;
int i = l + 1; //arr[lt+1...i) == v;
while (i < gt){
if(arr[i] == v){
i++;
}else if(arr[i] < v){
SortUtil.swap(arr,i,lt+1);
lt++;
i++;
}else{ // arr[i] > v
SortUtil.swap(arr,i,gt-1);
gt--;
}
}
SortUtil.swap(arr,l,lt);
//上面元素互換之後arr[lt] = v; 所以 <v 部分的遞歸調用排序的時候我們只要到lt-1即可
//==v 部分就不再需要排序了
quickSort(arr,l,lt- 1); // <v 部分排序
quickSort(arr,gt,r); // >v 部分排序
}
###4.測試
@Test
public void test(){
int arr[] = {0,1,1,2,3,3,4,8,7,6,12,22,65};
// int arr[] = {6,3,8,2,9,1};
System.out.println("排序前");
printArr(arr);
int[] data = quickSort(arr);
System.out.println("排序後");
printArr(data);
}
public void printArr(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
System.out.println();
}
###5.總結
快速排序在大多數情況下都是適用的,尤其在數據量大的時候性能優越性更加明顯。
特點:
1.記錄非順次地移動導致排序方法是不穩定的。
2.排序過程中需要定位表的下界和上界,所以適合用於順序結構,很難用於鏈式結構。
3.適合初始記錄無序,n較大的情況,當n較大時,在平均情況下快速排序是所有內部排序中速度最快的一種。
###6.聯繫方式
QQ:1509815887