快速排序

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章