排序 - 快速排序(C++)

前言的前言:

若是看不懂這些乾巴巴的理論文字,那就先不用看了,下面代碼中有詳細的註釋,你們能夠先跟着代碼走幾遍,回過頭來再看這些文字描述,總之:紙上得來終覺淺,絕知此事要躬行。

前言:

排序算法哪家強,從實際應用的角度上將,快排表現很好。很天然地,人們會以爲短數組比長數組更好處理,所以可能會想到將原始數組分爲若干各子部分而後分別進行排序。快速排序就是基於分治法的排序算法,這不過它更多地側重於「分」,沒有明顯的「合」過程。ios

快速排序的要點:

1.快排算法的主要思想:算法

(1)從待排序序列S中「任意」選擇一個記錄k做爲軸值(Pivot)。
(2)將剩餘的記錄分割(partition)成左子序列L和右子序列R。
(3)L中全部記錄都小於或等於k,R中記錄都大於等於k,所以k正好位於正確的位置。
(4)對子序列L和R遞歸進行快速排序,直到子序列中只含有0或1個元素,退出遞歸。

2.如何選擇軸值pivot,這對快排的時間性能影響很大,軸值的選擇應儘可能使得序列能夠據此劃分爲均勻的兩半。

3.如何實現最關鍵的分割的過程:最簡單的分割方法就是左指針l和右指針r(下標)分別從序列的左端、右端向序列中間掃描;左邊越過那些小於等於pivot的值,停在第一個大於pivot的值Kl;右邊越過那些大於等於pivot的值,停在第一個小於pivot的值Kr;交換逆置記錄Kl和Kr;從交換後的位置,繼續從左右向中間掃描,發現並交換逆置記錄對,直到l、r交叉而整個序列掃描完畢。這種方法適用於須要處理l、r邊界,軸值最後定位等狀況。
4. 因爲遞歸開銷過大,因此當子序列足夠短時,咱們採用插入排序來完成對子序列的排序。

5. 快速排序之因此快是由於:它每次選定軸值pivot並進行劃分子集(分割交換)後該軸值被一次性的放到了他最終該放到的位置。數組


下面以一趟分割交換爲例:

下標函數

0性能

1ui

2spa

3指針

4code

5對象

6

7

初始

25

34

45

32

34`

12

29

64

選基準

25

34

45

29

34`

12

32

64

分割

25

12

45

29

34`

34

32

64

分割

25

12

29

45

34`

34

32

64

基準定位

25

12

29

32

最終位置

34`

34

45

64

完整代碼以下:(pivot採用取頭、中、尾中位數、分割採用上述介紹的方法)

//#pragma once
//QuickSort.h

#include <iostream>
using namespace std;

const int Cutoff = 28;  //閾值,當子序列元素個數小於Cutoff時,採用簡單排序

template <class T>
class Quick
{
private:
	T * A;  //用來存儲待排序列的數組
	int N;  //待排序列元素個數

public:
	Quick(int size);  //構造函數
	~Quick() { delete[] A; }  //析構函數
	void QuickSort(int Left, int Right);  //遞歸進行快排
	T Median3(int Left, int Right);  //選主原(軸值)- 取頭、中、尾的中位數
	friend void Swap(T* a, T* b);  //交換兩個數 - 友元函數
	void InsertSort(T* B, int Nb);  //當子序列元素個數小於閾值時調用插入排序
	void Print();  //輸出結果 
};

//Quick類的實現
//構造函數初始化
template <class T> Quick<T>::Quick(int size)
{
	N = size;
	A = new T[N];

	for (int i = 0; i < N; i++)
		cin >> A[i];
}

//交換兩個數
template <class T> Swap(T* a, T* b)
{
	int tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
}

//輸出結果
template <class T> void Quick<T>::Print()
{
	for (int i = 0; i < N; ++i)
	{
		cout << A[i] << " ";
	}
}

//插入排序
template <class T> void Quick<T>::InsertSort(T* B, int Nb)
{
	int tmp, p, i;
	for (p = 1; p < Nb; ++p)  //總共摸取Nb-1張牌
	{
		tmp = B[p];  //當前摸到手中的一張牌
		for (i = p; i > 0 && A[i - 1] > tmp; i--)  //i不少狀況下都是用來控制循環次數的
		{
			A[i] = A[i - 1];
		}
		A[i] = tmp;  //新牌歸位
	}
}

//選軸值pivot
template <class T>  T Quick<T>::Median3(int Left, int Right)
{
	int Center = (Left + Right) / 2;

	if (A[Left] > A[Center])
		Swap(&A[Left], &A[Center]);
	if (A[Left] > A[Right])
		Swap(&A[Left], &A[Right]);
	if (A[Center] > A[Right])
		Swap(&A[Center], &A[Right]);
	//此時A[Left] <= A[Center] <= A[Right],爲了接下來的分割過程,將軸值藏到右邊
	//將軸值藏到子序列的右端只是爲了避免影響分割過程
	//分割時只需考慮A[Left+1] - A[Right-2]的區間
	Swap(&A[Center], &A[Right - 1]);
	//返回軸值
	return A[Right - 1];
}

//遞歸分割
template <class T> void Quick<T>::QuickSort(int Left, int Right)
{
	int Pivot, Low, High;  //基準、左、右指針

	if (Cutoff < Right - Left)  //若是序列元素充分多,則進入快排
	{
		Pivot = Median3(Left, Right);
		Low = Left;
		High = Right - 1;

		//分割過程
		while (true)
		{
			while (A[++Low] < Pivot);
			while (A[--High] > Pivot);
			if (Low < High)
				Swap(&A[Low], &A[High]);
			else
				break;
		}
		//將當前子序列的軸值一次性的放到他最終所在的位置上 - Low
		Swap(&A[Low], &A[Right - 1]);
		//當前子序列分割完成,遞歸進入更小一層子序列的分割
		QuickSort(Left, Low - 1);  //遞歸解決左邊子序列
		QuickSort(Low + 1, Right);  //遞歸解決右邊子序列
	}
	else
		InsertSort(A + Left, Right - Left + 1);  //當子序列足夠短時,採用插入排序
}

int main()
{
	int N;  //待排元素個數
	cin >> N;

	//定義Quick對象QuickA
	Quick<int> QuickA(N);
	//快速排序
	QuickA.QuickSort(0, N - 1);
	//輸出結果
	QuickA.Print();

	return 0;
}

運行結果: