天天看點

排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作

快速排序也是一種複雜度為 O ( n l o g n ) O(nlogn) O(nlogn)的排序算法,但它的效率比同等複雜的排序算法還要高,是以在實踐中會經常使用。

原理

快速排序的思想是将數組分為兩個部分,一部分小于 v v v,另一部分大于 v v v,這個 v v v也稱為基準數,一般選取數組第一個數作為基準數。它的實作效果是這樣的,當分為兩個數組後,每個單獨的數組又可以使用該方法排序,直到隻有最後一個元素,那這裡又可以使用遞歸方法了。我們将數組分為兩部分的過程稱為partition。

排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作

partition的實作思路如下:假設第一個元素為基準點,并将其索引記為 l l l,此時已經有一部分資料進行了整理,得到了 < v <v <v,和 > v >v >v的部分,規定 a r r [ l + 1... j ] < v arr[l+1...j]<v arr[l+1...j]<v, a r r [ j + 1... i − 1 ] > v arr[j+1...i-1]>v arr[j+1...i−1]>v, i i i表示周遊中目前元素索引。

排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作

如果 e > v e>v e>v就很簡單了,直接将索引值向後移動一位即可

排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作

如果 e < v e<v e<v,稍微麻煩一點,将 > v >v >v部分的第一個資料與 e e e交換,同時 j + + , i + + j++,i++ j++,i++即可。

排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作
排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作
排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作

周遊完成後,将 v v v與 a r r [ j ] arr[j] arr[j]交換, v v v也就放在了正确的位置。

排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作

此時 a r r [ l . . . j − 1 ] < v arr[l...j-1]<v arr[l...j−1]<v, a r r [ j + 1... i ] > v arr[j+1...i]>v arr[j+1...i]>v, a r r [ j ] = v arr[j]=v arr[j]=v,接下來對兩部分數組分别排序即可。

梳理以上過程,可得快排的實作步驟:

1.partition将數組正确分為兩部分

2.對兩部分遞歸partition,直至遞歸結束。

實作

#include <iostream>
#include <algorithm>
template <typename T>
int partition(T arr[], int l, int r){
	T v = arr[l];
	//arr[l+1,j]<v,arr[j+1,i)>v
	int j=l;
	for(int i=l+1; i<=r; i++){ //注意是<=r,而不是<r
		if(arr[i]<v){
			swap(arr[i],arr[j+1]);
			j++;
		}
	}
	swap(arr[j],arr[l]);
	return j;
}
template <typename T>
void __quickSort(T 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);
}
template <typename T)
void quickSort(T arr[], int n){
	__quickSort(arr, 0, n-1);
}
           

優化

首先,對于小規模數組排序可以采用插入排序,提高小規模數組排序運作速度。具體代碼隻需要将遞歸結束的條件改為插入排序。

template <typename T>
void __quickSort(T arr[], int l, int r){
	if(r-l<=15){
		insertionSort(arr,l,r);   //具體插入排序的代碼可以參考之前的文章
		return;
	}
	int p = partition(arr, l ,r);
	__quickSort(arr, l, p-1);
	__quickSort(arr, p+1, r);
}
           

但這隻是一個小小的修改,此時的快速排序有一個緻命的弱點會使時間複雜度變為 O ( n 2 ) O(n^2) O(n2)。這個問題就出現在解決近乎有序數組上,如果一個有序數組,快速排序每次選擇第一個元素作為基準值,在partition操作後基準值的位置幾乎沒有移動,那就相當于 n n n個資料被分為1 和 n − 1 n-1 n−1個,那麼到最後就需要遞歸n次,同時partition的操作時間複雜度也是 O ( n ) O(n) O(n),此時的複雜度就退化為 O ( n 2 ) O(n^2) O(n2)了。是以如果能夠盡量選擇中間的元素,那麼複雜度就能夠達到 O ( n l o g n ) O(nlogn) O(nlogn)。

排序算法-快速排序(快排、雙路快排、三路快排)(1) C++實作

為了解決這個問題,我們可以随機選擇一個數作為基準數,這樣就可以極大的減小每次partition後數組的不均衡性。此時在最差的情況下快排的時間複雜度仍然為 O ( n 2 ) O(n^2) O(n2),但是這種機率是非常低的,它的時間複雜度期望為 O ( n l o g n ) O(nlogn) O(nlogn)。

#include <iostream>
#include <algorithm>
#include <ctime>
template <typename T>
int partition(T arr[], int l, int r){
	swap(arr[l],arr[rand()%(r-l+1)+l);
	T v = arr[l];
	//arr[l+1,j]<v,arr[j+1,i)>v
	int j=l;
	for(int i=l+1; i<=r; i++){ //注意是<=r,而不是<r
		if(arr[i]<v){
			swap(arr[i],arr[j+1]);
			j++;
		}
	}
	swap(arr[j],arr[l]);
	return j;
}
template <typename T>
void __quickSort(T arr[], int l, int r){
	if(r-l<=15){
		insertionSort(arr,l,r); 
		return;
	}
	int p = partition(arr, l ,r);
	__quickSort(arr, l, p-1);
	__quickSort(arr, p+1, r);
}
template <typename T)
void quickSort(T arr[], int n){
	srand(time(NULL));
	__quickSort(arr, 0, n-1);
}
           

參考:https://coding.imooc.com/learn/list/71.html

繼續閱讀