C++快速排序及優(yōu)化方案詳解
1. 快速排序的流程
- 首先設(shè)定一個(gè)分界值,通過(guò)該分界值將數(shù)組分成左右兩部分。
- 將大于分界值的數(shù)據(jù)集中到數(shù)組右邊,小于分界值的數(shù)據(jù)集中到數(shù)組的左邊,而等于分分界值的部分放在相對(duì)中間的部分。此時(shí),左邊部分中各元素都小于分界值,而右邊部分中各元素都大于分界值,相對(duì)中間的部分的的數(shù)據(jù)等于分界值。
- 然后,左邊和右邊的數(shù)據(jù)可以獨(dú)立排序。對(duì)于左側(cè)的數(shù)組數(shù)據(jù),又可以取一個(gè)分界值,將該部分?jǐn)?shù)據(jù)分成左右兩部分,同樣在左邊放置較小值,右邊放置較大值。右側(cè)的數(shù)組數(shù)據(jù)也可以做類似處理。
- 重復(fù)上述過(guò)程,可以看出,這是一個(gè)遞歸定義。通過(guò)遞歸將左側(cè)部分排好序后,再遞歸排好右側(cè)部分的順序。當(dāng)左、右兩個(gè)部分各數(shù)據(jù)排序完成后,整個(gè)數(shù)組的排序也就完成了。
2. 快速排序的實(shí)現(xiàn)
原始的快速排序相對(duì)來(lái)說(shuō)會(huì)比較熟悉點(diǎn),大致的部分流程圖如圖所示:

直接上代碼:
#include<iostream>
#include<string>
#include<vector>
#include<time.h>
using namespace std;
//三色旗原理代碼部分
pair<int, int> Quick(vector<int>& vec, int L, int R)
{
int temp = vec[L];//基準(zhǔn)值
int i = L - 1;//左邊界
int j = R + 1;//右邊界
int index = L;//交換變量
while (index < j)
{
if (vec[index] < temp)
{
swap(vec[index++], vec[++i]);
}
else if (vec[index] > temp)
{
swap(vec[index], vec[--j]);
}
else
{
index++;
}
}
pair<int, int> p = make_pair(i, j);//存儲(chǔ)下次排序的左右邊界,
return p;
}
void Quick_sort(vector<int>& vec, int L, int R)
{
if (L >= R)return;//遞歸結(jié)束的標(biāo)志
pair<int, int> p = Quick(vec, L, R);
Quick_sort(vec, L, p.first);//將數(shù)組的左部分進(jìn)行排序
Quick_sort(vec, p.second, R);//將數(shù)組的右部分進(jìn)行排序
}
int main()
{
vector<int> vec = { 5,6,3,7,2,8 };
Quick_sort(vec, 0, vec.size() - 1);
for (auto it : vec)
{
cout << it << " ";
}
return 0;
}時(shí)間復(fù)雜度計(jì)算
快速排序的最優(yōu)的情況時(shí)的時(shí)間復(fù)雜度為O(N*logN) 因?yàn)樽顑?yōu)解在排序過(guò)程中每次都利用遞歸將數(shù)組不斷二分,并且不斷二分過(guò)程次相當(dāng)于二分法,而二分的時(shí)間復(fù)雜度為logN(這里的log是以2為底的),每次二分的各個(gè)子數(shù)組的和均為n個(gè)元素,在排序過(guò)程中所有元素在不同的遞歸過(guò)程中均會(huì)被遍歷比較,所以每次都會(huì)有N個(gè)元素會(huì)被遍歷,即時(shí)間復(fù)雜度為:O(N*logN) 最壞的情況時(shí)的時(shí)間復(fù)雜度為O(N^2) 這種情況是當(dāng)數(shù)組有序的情況下,每次基準(zhǔn)值都是取了數(shù)組中的最小值或最大值,而且每次遞歸都只是排除了基準(zhǔn)值那個(gè)元素,這里就很像冒泡排序不斷將子數(shù)組中最值排除掉,而冒泡排序的時(shí)間復(fù)雜度為O(N^2)。 因此最壞情況下的時(shí)間復(fù)雜度為O(N^2)。
3. 快速排序優(yōu)化
3.1 隨機(jī)獲取基準(zhǔn)值進(jìn)行優(yōu)化
上一節(jié)我們自己動(dòng)手寫的一個(gè)快速排序的算法,在進(jìn)行毫無(wú)規(guī)則的數(shù)組排序過(guò)程中表現(xiàn)得非常好,但是,當(dāng)我們?nèi)グ偃f(wàn),十萬(wàn)級(jí)別量的數(shù)據(jù)進(jìn)行排序并且高度的有序化時(shí),我們會(huì)發(fā)現(xiàn)此時(shí)程序運(yùn)行的過(guò)程中,發(fā)現(xiàn)快速排序的效率變得異常的低下,會(huì)比相同數(shù)據(jù)量的數(shù)組中的數(shù)據(jù)是毫無(wú)規(guī)則時(shí)效率低得多了,近似退回了O(n^2)的復(fù)雜度,而此時(shí)程序則需要等待非常長(zhǎng)的時(shí)間才能執(zhí)行完全。 在編寫快排代碼的過(guò)程中,我們是利用遞歸來(lái)對(duì)數(shù)組進(jìn)行劃分的,這和歸并排序里面的利用遞歸使得不斷的二分才能達(dá)到O(n*logn)的時(shí)間復(fù)雜度相似,在快速排序中最好的情況就是如同歸并一樣,每次都是二分,這種情況的時(shí)間復(fù)雜度是最佳的時(shí)間復(fù)雜度O(n*logn)。如圖:

但是當(dāng)數(shù)組高度有序化或者數(shù)組本來(lái)就是有序的時(shí)候,這個(gè)時(shí)候數(shù)組數(shù)據(jù)呈現(xiàn)出一邊倒的趨勢(shì)此時(shí)快速排序的時(shí)間復(fù)雜度達(dá)到最壞的情況逼近O(n^2) 甚至達(dá)到為O(n^2),這樣的快速排序遠(yuǎn)遠(yuǎn)達(dá)不到我們的需求,如圖:

在這種情況下我們可以通過(guò)隨機(jī)獲取每次排序的基準(zhǔn)值來(lái)進(jìn)行優(yōu)化int temp = vec[rand()%(R-L+1)+L];,同時(shí)通過(guò)百萬(wàn)、十萬(wàn)級(jí)別量的數(shù)據(jù)量來(lái)計(jì)算程序運(yùn)行的時(shí)間比較時(shí)間復(fù)雜度。
計(jì)算時(shí)間的代碼如下:
clock_t startime, endtime;
startime = clock();
....//中間代碼
endtime = clock();
cout << (double)(endtime - startime)/ CLOCKS_PER_SEC << endl;
通過(guò)隨機(jī)獲取基準(zhǔn)值優(yōu)化代碼如下:
#include<iostream>
#include<string>
#include<vector>
#include<time.h>
using namespace std;
//三色旗原理代碼部分
pair<int, int> Quick(vector<int>& vec, int L, int R)
{
int temp = vec[rand()%(R-L+1)+L];//隨機(jī)獲取基準(zhǔn)值進(jìn)行優(yōu)化
//int temp = vec[L];//沒有獲取隨機(jī)基準(zhǔn)值
int i = L - 1;//左邊界
int j = R + 1;//右邊界
int index = L;//交換變量
while (index < j)
{
if (vec[index] < temp)
{
swap(vec[index++], vec[++i]);
}
else if (vec[index] > temp)
{
swap(vec[index], vec[--j]);
}
else
{
index++;
}
}
pair<int, int> p = make_pair(i, j);//存儲(chǔ)下次排序的左右邊界,
return p;
}
void Quick_sort(vector<int>& vec, int L, int R)
{
if (L >= R)return;//遞歸結(jié)束的標(biāo)志
pair<int, int> p = Quick(vec, L, R);
Quick_sort(vec, L, p.first);//將數(shù)組的左部分進(jìn)行排序
Quick_sort(vec, p.second, R);//將數(shù)組的右部分進(jìn)行排序
}
int main()
{
clock_t startime, endtime;
startime = clock();//開始時(shí)間
vector<int> vec;
for (int i = 0; i < 100000; i++) {
//(在這里使用十萬(wàn)級(jí)別的數(shù)據(jù)量 完全有序的數(shù)組進(jìn)行計(jì)算時(shí)間復(fù)雜度 百萬(wàn)級(jí)別的數(shù)據(jù)量由于程序執(zhí)行時(shí)間太長(zhǎng) 不例舉)
vec.push_back(i);
}
Quick_sort(vec, 0, vec.size() - 1);
/*for (auto it : vec)//在這里不進(jìn)行輸出,數(shù)據(jù)量太大
{
cout << it << " ";
}*/
endtime = clock();//結(jié)束時(shí)間
cout << (double)(endtime - startime)/ CLOCKS_PER_SEC << endl;
//在這里沒有定義單位,只通過(guò)數(shù)值進(jìn)行比較來(lái)判斷
return 0;
}此時(shí)沒有經(jīng)過(guò)優(yōu)化的代碼執(zhí)行時(shí)間如圖:

經(jīng)過(guò)優(yōu)化的代碼執(zhí)行時(shí)間如圖:

兩者相對(duì)比較而言進(jìn)行優(yōu)化的時(shí)間復(fù)雜度遠(yuǎn)遠(yuǎn)小于未經(jīng)過(guò)優(yōu)化的。但是在數(shù)組里面的數(shù)據(jù)是亂序的情況下,經(jīng)過(guò)優(yōu)化的時(shí)間復(fù)雜度會(huì)偶爾出現(xiàn)略高于未經(jīng)過(guò)優(yōu)化的情況,但影響并不是很大。
3.2二路快速排序
接著前面所介紹來(lái)說(shuō),當(dāng)我們排序的是一個(gè)近乎有序的序列時(shí),快速排序會(huì)退化到一個(gè)O(n^2) 級(jí)別的排序算法,而我們對(duì)此的就是引入了隨機(jī)化快速排序算法;但是問題又來(lái)了,當(dāng)我們排序的數(shù)據(jù)是一個(gè)數(shù)值重復(fù)率非常高的序列時(shí),或者是輸入的數(shù)據(jù)完全相同的情況時(shí),此時(shí)隨機(jī)化快速排序算法就不再起作用了,而將會(huì)再次退化為一個(gè)O(n^2) 級(jí)別的排序算法。 在這種情況下不管是>=temp還是<=temp,當(dāng)我們的序列中存在大量重復(fù)的元素時(shí),排序完成之后就會(huì)將整個(gè)數(shù)組序列分成兩個(gè)極度不平衡的部分,甚至更惡劣的情況是所有數(shù)據(jù)均一樣而出現(xiàn)一邊倒的趨勢(shì),所以又退化到了O(n^2) 級(jí)別的時(shí)間復(fù)雜度,這是因?yàn)閷?duì)于每一個(gè)"基準(zhǔn)"元素來(lái)說(shuō),重復(fù)的元素太多了,如果我們選的"基準(zhǔn)"元素稍微有一點(diǎn)的不平衡,那么就會(huì)導(dǎo)致兩部分的差距非常大;即時(shí)我們的"基準(zhǔn)"元素選在了一個(gè)平衡的位置,但是由于等于"基準(zhǔn)"元素的元素也非常多,也會(huì)使得序列被分成兩個(gè)及其不平衡的部分,那么在這種情況下快速排序就又會(huì)退化成O(n^2) 級(jí)別的排序算法。 在這里我們可以使用二路快速排序進(jìn)行優(yōu)化。
原理:
前面所敘述的快速排序算法是將>temp和<temp兩個(gè)部分元素都放在索引值i所指向的位置的左邊部分,而雙路快速排序則是使用兩個(gè)索引值(i、j)用來(lái)遍歷我們的序列,將<temp的元素放在索引 i 所指向位置的左邊,而將>temp的元素放在索引j所指向位置的右邊。
思想:
1、首先從左邊的i索引往右邊遍歷,如果i指向的元素<temp,那直接將i++移動(dòng)到下一個(gè)位置,直道i指向的元素>=temp則停止。
2、然后使用j索引從右邊開始往左邊遍歷,如果j指向的元素>temp,那直接將j–移動(dòng)到下一個(gè)位置,直道j指向的元素<=temp則停止
3、此時(shí)i之前的元素都已經(jīng)歸并為<temp的部分了,而j之后的元素也都已經(jīng)歸并為>temp的部分了,此時(shí)只需要將vec[i]和vec[j]交換位置即可。這樣就可以避免出現(xiàn)=temp的元素全部集中在某一個(gè)部分,這正是雙路排序算法的一個(gè)核心。但是當(dāng)需要排序的數(shù)據(jù)長(zhǎng)度比較小時(shí),此時(shí)使用插入排序的性能比較好,所以我們結(jié)合快速排序和插入排序進(jìn)行一個(gè)優(yōu)化快速排序。

具體實(shí)現(xiàn)代碼:
#include<iostream>
#include<vector>
#include<time.h>
using namespace std;
void Insert_sort(vector<int>& vec,int L,int R) {
for (int i = L+1; i < R; i++) {//用i來(lái)記錄無(wú)序表的第一個(gè)值的下標(biāo)
int j = i - 1;//用來(lái)記錄前面有序列的最后一個(gè)值的下標(biāo)
int temp = vec[i];//記錄無(wú)序列的第一個(gè)值的值
for (; j >= 0; j--) {
if (vec[j] > temp) {
vec[j + 1] = vec[j];//將有序表中的元素后移。
}
else {
break;//當(dāng)無(wú)序表中的第一個(gè)值不比有序表中的最后一個(gè)值小時(shí),跳出循環(huán)
}
}
vec[j + 1] = temp;//將后移后的空值補(bǔ)上無(wú)序表中的第一個(gè)值。
}
}
int qucikSort(vector<int>& vec, int L, int R)
{
swap(vec[L], vec[rand() % (R - L + 1) + L]);// 隨機(jī)產(chǎn)生"基準(zhǔn)"元素所在位置,并與第一個(gè)元素交換位置
int temp = vec[L]; // 將第一個(gè)元素作為"基準(zhǔn)"元素
// 使用i索引從左到右遍歷,使用j索引從右到左遍歷
int i = L + 1;// 索引值i初始化為第二個(gè)元素位置
int j = R;// 索引值j初始化為最后一個(gè)元素位置
while (true) {
while ((i < R) && (vec[i] < temp)) i++;// 使用索引i從左往右遍歷直到 vec[i] < temp
while ((j > L + 1) && (vec[j] > temp)) j--;// 使用索引j從右往左遍歷直到 vec[j] > temp
if (i >= j) break;// 退出循環(huán)的條件
swap(vec[i], vec[j]);// 將 vec[i] 與 vec[j] 交換位置
i++;
j--;
}
swap(vec[L], vec[j]);// 最后將"基準(zhǔn)"元素temp放置到合適的位置
return j;
}
void quick(vector<int>& vec, int L, int R)
{
if (R - L <= 40) {//當(dāng)數(shù)據(jù)量比較小時(shí)我們采用插入排序進(jìn)行
Insert_sort(vec, L, R);
return;
}
int p = qucikSort(vec, L, R);// 對(duì)vec[left...right]區(qū)間元素進(jìn)行排序操作,找到"基準(zhǔn)"元素
quick(vec, L, p - 1);// 對(duì)基準(zhǔn)元素之前的序列遞歸
quick(vec, p + 1, R);// 對(duì)基準(zhǔn)元素之后的序列遞歸
}
int main()
{
clock_t startime, endtime;
startime = clock();//開始時(shí)間
vector<int> vec;
srand(time(0));
for (int i = 0; i < 100000; i++) {
//(在這里使用十萬(wàn)級(jí)別的數(shù)據(jù)量,完全有序的數(shù)組進(jìn)行計(jì)算時(shí)間復(fù)雜度
// 百萬(wàn)級(jí)別的數(shù)據(jù)量由于程序執(zhí)行時(shí)間太長(zhǎng),不例舉)
vec.push_back(rand()%100);
}
quick(vec, 0, vec.size() - 1);
//for (auto it : vec)//在這里不進(jìn)行輸出,數(shù)據(jù)量太大
//{
// cout << it << " ";
//}
endtime = clock();//結(jié)束時(shí)間
cout << (double)(endtime - startime) / CLOCKS_PER_SEC << endl;
//在這里沒有定義單位,只通過(guò)數(shù)值進(jìn)行比較來(lái)判斷
return 0;
}在這里隨機(jī)數(shù)產(chǎn)生的數(shù)據(jù)進(jìn)行性能分析,如圖第一個(gè)數(shù)據(jù)是未經(jīng)過(guò)優(yōu)化的時(shí)執(zhí)行一個(gè)利用隨機(jī)生成數(shù)亂序并且重復(fù)率較高的執(zhí)行時(shí)間,第二個(gè)數(shù)據(jù)是二路快速排序的執(zhí)行時(shí)間。在這里執(zhí)行時(shí)間相差不多是因?yàn)檫@里我們難以得到一個(gè)重復(fù)率非常高的一組數(shù)據(jù),但是實(shí)際上雙路快速排序優(yōu)化的結(jié)果還是比較理想的。

4. 總結(jié)
在上述優(yōu)化的過(guò)程中, 對(duì)于原始的快排來(lái)說(shuō),當(dāng)重復(fù)率低,并且數(shù)組的有序化低是具有很好的效率,但是在應(yīng)對(duì)大量的規(guī)則性比較強(qiáng)的數(shù)據(jù)時(shí),效率是跟不上。而隨機(jī)快速排序只是獲取了一個(gè)隨機(jī)基準(zhǔn)值來(lái)應(yīng)對(duì)數(shù)據(jù)有序化程度比較高的情況下來(lái)進(jìn)行優(yōu)化。但是二路快速排序結(jié)合了隨機(jī)快排和插入排序來(lái)應(yīng)對(duì)能夠出現(xiàn)的所有情況來(lái) 達(dá)到比較好的效果。
到此這篇關(guān)于C++快速排序及優(yōu)化方案詳解的文章就介紹到這了,更多相關(guān)C++快速排序及優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
QML中動(dòng)態(tài)與靜態(tài)模型應(yīng)用詳解
QML是一種描述性的腳本語(yǔ)言,文件格式以.qml結(jié)尾。語(yǔ)法格式非常像CSS(參考后文具體例子),但又支持javascript形式的編程控制。QtDesigner可以設(shè)計(jì)出·ui界面文件,但是不支持和Qt原生C++代碼的交互2022-08-08
C語(yǔ)言數(shù)組越界引發(fā)的死循環(huán)問題解決
本文主要介紹了C語(yǔ)言數(shù)組越界引發(fā)的死循環(huán)問題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
C語(yǔ)言中用于產(chǎn)生隨機(jī)數(shù)的函數(shù)使用方法總結(jié)
這篇文章主要介紹了C語(yǔ)言中用于產(chǎn)生隨機(jī)數(shù)的函數(shù)使用方法總結(jié),分別介紹了rand()函數(shù)和srand()函數(shù)以及封裝出的arc4random()函數(shù),需要的朋友可以參考下2016-05-05
C++的try塊與異常處理及調(diào)試技術(shù)實(shí)例解析
這篇文章主要介紹了C++的try塊與異常處理及調(diào)試技術(shù)實(shí)例解析,有助于讀者加深對(duì)try塊調(diào)試技術(shù)的認(rèn)識(shí),需要的朋友可以參考下2014-07-07
C++使用宏實(shí)現(xiàn)動(dòng)態(tài)庫(kù)加載
開發(fā)的時(shí)候,有些項(xiàng)目不能靜態(tài)鏈接動(dòng)態(tài)庫(kù),需要程序運(yùn)行時(shí)加載動(dòng)態(tài)庫(kù)。本文將使用宏來(lái)實(shí)現(xiàn)動(dòng)態(tài)庫(kù)的加載,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12
C++多線程實(shí)現(xiàn)TCP服務(wù)器端同時(shí)和多個(gè)客戶端通信
通訊建立后首先由服務(wù)器端發(fā)送消息,客戶端接收消息;接著客戶端發(fā)送消息,服務(wù)器端接收消息,實(shí)現(xiàn)交互發(fā)送消息。本文主要介紹了C++多線程實(shí)現(xiàn)TCP服務(wù)器端同時(shí)和多個(gè)客戶端通信,感興趣的可以了解一下2021-05-05
C++簡(jiǎn)單實(shí)現(xiàn)與分析二叉搜索樹流程
二叉搜索樹作為一個(gè)經(jīng)典的數(shù)據(jù)結(jié)構(gòu),具有鏈表的快速插入與刪除的特點(diǎn),同時(shí)查詢效率也很優(yōu)秀,所以應(yīng)用十分廣泛。本文將詳細(xì)講講二叉搜索樹的C++實(shí)現(xiàn),需要的可以參考一下2022-08-08

