1. 算法思想
“分而治之”的思想。平均時間複雜度爲O(nlogn),每次遞歸都使主元pivot左邊的元素不超過它、右邊的元素不小於它(即將其放置於它最終該在位置,這也是快速排序之所以快的原因)。其關鍵在於主元的選定和子集的劃分。
1.1 主元的選定
(1) pivot=A[0],當A本身有序時,每次劃分都有一個子集是空集,導致最終的時間複雜度爲O(n2)
(2) 隨機取(使用rand函數),比較麻煩
(3) 取頭、中、尾的中位數
1.2 子集劃分
使用雙指針法,i=0,j=n-1
,當A[j]<=pivot
時,轉去找第一個A[i]>=pivot
的位置,然後交換。
考慮一個問題:如果有元素等於pivot怎麼辦?應該停下來交換而不是不理它而繼續i++,這樣當序列全是一樣的元素的情況下,時間複雜度依舊退化爲O(n2)。
1.4 算法實現
void quickSort(int a[], int left, int right) { //也可以寫成int *a
if (left >= right) return;
int i = left, j = right;
while (i < j) {
//下面兩個while循環的順序不可顛倒,不然程序執行結果錯誤
while (i < j&&a[j] >= a[left]) j--;
while (i < j&&a[i] <= a[left]) i++;
swap(a[i], a[j]);
}
swap(a[i], a[left]); //退出循環後,i的位置就是left最終應該在的位置
quickSort(a, left, i - 1);
quickSort(a, j + 1, right);
}
或者
void quickSort(int a[], int left, int right) { //也可以寫成int *a
if (left >= right) return;
int i = left, j = right, temp=a[left]; //將a[left]存至臨時變量temp
while (i < j) {
while (i < j&&a[j] >= temp) j--;
a[i] = a[j]; //用替換代替交換
while (i < j&&a[i] <= temp) i++;
a[j] = a[i];
}
swap(a[i], temp); //退出循環後,i的位置就是left最終應該在的位置
quickSort(a, left, i - 1);
quickSort(a, j + 1, right);
}
2. 算法優化
2.1 優化選取樞紐
如果pivot選得不好,快速排序也“快”不起來。最壞的情況是每次選取的pivot都將集合分成一個空集+另一個集合,這種情況時間複雜度退化爲O(n2)。那麼可以怎樣優化呢?
三數取中法
取三個元素先排序,將中間數作爲樞紐,一般選取左端、右端和中間三個數。從概率上來說,取三個數均爲最小或最大的可能性是微乎其微的。用rand取隨機數當然可以做到隨機,但是要知道隨機數生成器本身還會帶來時間上的開銷。
隨機數法
通過生成隨機數的方法,隨機選取數組的一個元素作爲pivot。
2.2 小規模數據的處理
快排存在的問題:使用的遞歸算法……對於小規模數據可能還不如插入排序快。
解決方案:當遞歸的數據規模小於閾值(據《大話數據結構》,有資料認爲7比較合適,也有認爲50更合理,實際應用可適當調整)時,停止遞歸,直接調用簡單排序算法(如直接插入排序——是簡單排序中性能最好的)。
3.例題
https://www.luogu.com.cn/problem/P1177
題目描述
AC代碼
由於最後一個測試用例很變態,100000個一樣的數…我也只能面向測試用例編程了。判斷數組是否全部相等,如果是就不排序。
#include <cstdio>
//#include<stdlib.h>
//#include<time.h>
#include<algorithm>
using namespace std;
int a[100005];
void insertSort(int left, int right) {
for (int i = left + 1; i <= right; i++) {
int temp = a[i], j = i;
for (; j > left&&temp < a[j - 1]; j--) a[j] = a[j - 1];
a[j] = temp;
}
}
void quickSort(int left, int right) { //也可以寫成int *a
if (left >= right) return;
if (right - left + 1 <= 7) { //改爲使用插排
insertSort(left, right);
return;
}
/*int pivot = rand() % (right - left + 1) + left; //隨機數法確定pivot
swap(a[left], pivot);*/
int mid = left + (right - left) / 2; //三數取中法確定pivot
if (a[left] > a[right]) swap(a[left], a[right]);
if (a[mid] > a[right]) swap(a[mid], a[right]);
if (a[left] < a[mid]) swap(a[left], a[mid]);
int i = left, j = right;
while (i < j) {
while (i < j&&a[j] >= a[left]) j--;
while (i < j&&a[i] <= a[left]) i++;
swap(a[i], a[j]);
}
swap(a[i], a[left]);
quickSort(left, i - 1);
quickSort(j + 1, right);
}
int main() {
/*srand((int)time(NULL));*/
int n; scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
bool flag = 0;
for (int i = 1; i < n; i++) {
if (a[i] != a[0]) flag = 1;
}
if(flag) quickSort(0, n - 1); //面向最後一個測試用例 :)
for (int i = 0; i < n; i++) {
printf("%d", a[i]);
printf("%s", i == n - 1 ? "\n" : " ");
}
return 0;
}