高級排序算法(二)

快速排序(Quick Sort)

算法思想:在待排序表L[1...n]中任取一個元素pivot作為基準,通過一趟排序?qū)判虮韯澐譃楠毩⒌膬刹糠諰[1...k-1]和L[k+1...n]媳维,使得L[1...k-1]中所有元素小于pivot冰肴,L[k+1...n]中所有元素大于或等于pivot粉寞,則pivot放在了其最終位置L(k)上,這個過程稱為一趟快速排序肮之。而后分別遞歸地對兩個子表重復(fù)上述過程捣作,直至每部分內(nèi)只有一個元素或空為止测柠,即所有元素放在了最終的位置上主卫。

算法演示:

快速排序

基本代碼如下:

// 對arr[l...r]部分進行partition操作
// 返回p,使得arr[l...p-1] < arr[p]; arr[p+1...r] > arr[p]
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++) {
        if (arr[i] < v) {
            swap(arr[j + 1], arr[i]);
            j++;
        }
    }
    swap(arr[l], arr[j]);
    return j;
}

// 對arr[l...r]部分進行快速排序
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);
}

好了鹃愤,按照慣例我們將同時調(diào)用快速排序和歸并排序進行測試,看看哪種算法的運行效率更高完域,其結(jié)果如下(隨機數(shù)據(jù)):

Quick Sort : 0.029 s
Merge Sort : 0.03 s

我們從結(jié)果中發(fā)現(xiàn)快速排序算法的運行效率與優(yōu)化后的歸并排序算法的效率不相上下软吐。不知大家有沒有發(fā)現(xiàn)歸并排序算法前“優(yōu)化后”這三個字加粗顯示,這么做其實是為了想告訴大家吟税,我們的快速排序算法還有優(yōu)化的空間凹耙,而歸并排序算法已是目前最優(yōu)的結(jié)果。

那么肠仪,我們將怎么樣優(yōu)化快速排序呢肖抱?我們先回想一下對歸并排序的優(yōu)化操作,在歸并排序算法中异旧,我們針對數(shù)據(jù)較少時采用插入排序意述,類似的,我們也可以進行這樣的操作。

有一個重要的問題荤崇,我們始終忽略了拌屏。這就是我們沒有測試在近乎有序的隨機數(shù)據(jù)情況下,兩種排序算法的運行效率术荤。大家若學習過數(shù)據(jù)結(jié)構(gòu)這門課程就會知道倚喂,快速排序有個致命的缺陷——在處理近乎有序的隨機數(shù)據(jù)時,其時間復(fù)雜度會變?yōu)镺(n2)瓣戚。

為什么會成這種結(jié)果呢端圈?這是因為我們在對待排序表進行劃分時,不像歸并排序算法一樣一分為二子库,而是先找到一個元素pivot作為基準舱权,使得待排序列表在基準之前的元素均小于它,在基準后面的元素均大于它刚照。當快速排序算法處理近乎有序的隨機數(shù)據(jù)時刑巧,這種劃分操作就會類似于冒泡排序算法的處理操作,所以在這種情況下時間復(fù)雜度就變?yōu)镺(n2)无畔。

為了解決這種問題啊楚,我們就不再采用選用待排序表第一個元素作為基準(請大家不要被嚴奶奶版的數(shù)據(jù)結(jié)構(gòu)中快速排序算法的講解所束縛,推薦在學習數(shù)據(jù)結(jié)構(gòu)時翻閱“黑皮書”對數(shù)據(jù)結(jié)構(gòu)做進一步了解)浑彰,而選用待排序列表中盡可能中間的元素作為基準恭理,即隨機選擇一個數(shù)。(注:這里不過多敘述其原因郭变,具體請參考算法導(dǎo)論或“黑皮”版數(shù)據(jù)結(jié)構(gòu)與算法分析颜价。)

改進后的快速排序算法基本代碼如下:

// 對arr[l...r]部分進行partition操作
// 返回p,使得arr[l...p-1] < arr[p]; arr[p+1...r] > arr[p]
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++) {
        if (arr[i] < v) {
            swap(arr[j + 1], arr[i]);
            j++;
        }
    }
    swap(arr[l], arr[j]);
    return j;
}

// 對arr[l...r]部分進行快速排序
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) {

    srand(time(NULL));
    __quickSort(arr, 0, n - 1);
}

讓我們看看運行的結(jié)果吧(近乎有序的隨機數(shù)據(jù))诉濒。

Quick Sort : 0.035 s
Merge Sort : 0.005 s

我們發(fā)現(xiàn)快速排序算法的運行效率雖比上歸并排序周伦,但其性能已經(jīng)遠遠好于優(yōu)化前的性能。

除此之外未荒,我們的快速排序還可進行優(yōu)化专挪。例如在含有大量重復(fù)數(shù)據(jù)的情況下,我們的快速排序算法的運行效率依舊不高片排。這里我們向大家展示一下快速排序算法的龜速寨腔!

首先,我們按如下代碼修改main()中的代碼:

int main() {

    int n = 100000;
    int *arr_1 = SortTestHelper::generateRandomArray(n, 0, 10);
    int *arr_2 = SortTestHelper::copyIntArray(arr_1, n);

    SortTestHelper::testSort("Quick Sort", quickSort, arr_1, n);
    SortTestHelper::testSort("Merge Sort", mergeSort, arr_2, n);

    delete[] arr_1;
    delete[] arr_2;
    return 0;
}

然后我們運行程序率寡,看看其運行結(jié)果:

Quick Sort : 1.459 s
Merge Sort : 0.017 s

在處理含有大量重復(fù)數(shù)據(jù)時,快速排序算法的運行效率可稱為龜速耙惫病每界!這是因為我們在對待排序表進行劃分操作時,由于數(shù)據(jù)中含有大量的重復(fù)數(shù)據(jù)幻捏,會有很大概率上將待排序表劃分得極度不平衡,從而導(dǎo)致快速排序算法退化為時間復(fù)雜度為O(n2)篡九。

那么對于這種情況,我們優(yōu)化思路的算法演示為:

實際上榛臼,圖中兩側(cè)的數(shù)據(jù)應(yīng)該是如下圖所示:

二路快速排序

優(yōu)化的基本代碼如下:

// 對arr[l...r]部分進行partition操作
// 返回p,使得arr[l...p-1] < arr[p]; arr[p+1...r] > arr[p]
template<typename T>
int __partition2(T arr[], int l, int r) {

    swap(arr[l], arr[rand() % (r - l + 1) + l]);
    T v = arr[l];

    // arr[l+1...i) <= v; arr(j...r] >= v
    int i = l + 1, j = r;
    while (true) {
        while (i <= r && arr[i] < v) i++;
        while (j >= l + 1 && arr[j] > v) j--;
        if (i > j) break;
        swap(arr[i], arr[j]);
        i++;
        j--;
    }
    swap(arr[l], arr[j]);
    return j;
}

// 對arr[l...r]部分進行快速排序
template<typename T>
void __quickSort2(T arr[], int l, int r) {

    if (l >= r)
        return;

    int p = __partition2(arr, l, r);
    __quickSort2(arr, l, p - 1);
    __quickSort2(arr, p + 1, r);
}

template<typename T>
void quickSort2(T arr[], int n) {

    // 設(shè)置當前的時間值為種子,那么種子總是變化的航揉,所以以該種子產(chǎn)生的隨機數(shù)總是變化的
    srand(time(NULL));
    __quickSort2(arr, 0, n - 1);
}

那我們來看看這次優(yōu)化后的結(jié)果吧。

Quick Sort : 1.487 s
Merge Sort : 0.018 s
Quick Sort 2 : 0.016 s

通過這次優(yōu)化金刁,我們的快速排序算法的運行效率有了顯著地提升。實際上尤蛮,我們將這種方式的快速排序算法稱之為雙路快速排序算法。除此之外产捞,在處理含有大量重復(fù)數(shù)據(jù)的數(shù)據(jù)時醇锚,我們還有一個更為經(jīng)典的快速排序算法,通常我們將其稱為三路快速排序算法坯临。

其實這個排序算法的思路很簡單焊唬,其算法演示如下圖所示:

三路快速排序

三路快速排序算法的基本代碼如下:

// 三路快速排序
// 將arr[l...r]分為 < v; == v; > v 三部分
// 之后遞歸對 < v; > v 兩部分進行三路快速排序
template<typename T>
void __quickSort3(T arr[], int l, int r) {

    if (l >= r)
        return;

    // partition操作
    swap(arr[l], arr[rand() % (r - l + 1) + l]);
    T v = arr[l];

    // arr[l+1...lt] < v
    int lt = l;
    // arr[gt...r] > v
    int gt = r + 1;
    // arr[lt+1...i) == v
    int i = l + 1;

    while (i < gt) {
        if (arr[i] < v) {
            swap(arr[i], arr[lt + 1]);
            lt++;
            i++;
        } else if (arr[i] > v) {
            swap(arr[i], arr[gt - 1]);
            gt--;
        } else {
            // arr[i] == v
            i++;
        }
    }
    swap(arr[l], arr[lt]);

    __quickSort3(arr, l, lt - 1);
    __quickSort3(arr, gt, r);
}

template<typename T>
void quickSort3(T arr[], int n) {

    // 設(shè)置當前的時間值為種子,那么種子總是變化的,所以以該種子產(chǎn)生的隨機數(shù)總是變化的
    srand(time(NULL));
    __quickSort3(arr, 0, n - 1);
}

好了看靠,我們調(diào)用一些三路快速排序算法并運行程序赶促,其結(jié)果如下:

Quick Sort : 1.393 s
Merge Sort : 0.018 s
Quick Sort 2 : 0.015 s
Quick Sort 3 : 0.006 s
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挟炬,隨后出現(xiàn)的幾起案子鸥滨,更是在濱河造成了極大的恐慌,老刑警劉巖辟宗,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吝秕,居然都是意外死亡泊脐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門烁峭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來容客,“玉大人秕铛,你說我怎么就攤上這事∷跆簦” “怎么了但两?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長供置。 經(jīng)常有香客問我谨湘,道長,這世上最難降的妖魔是什么芥丧? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任紧阔,我火速辦了婚禮,結(jié)果婚禮上续担,老公的妹妹穿的比我還像新娘擅耽。我一直安慰自己,他們只是感情好物遇,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布乖仇。 她就那樣靜靜地躺著,像睡著了一般询兴。 火紅的嫁衣襯著肌膚如雪乃沙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天蕉朵,我揣著相機與錄音崔涂,去河邊找鬼。 笑死始衅,一個胖子當著我的面吹牛冷蚂,可吹牛的內(nèi)容都是我干的蝙茶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼隆夯,長吁一口氣:“原來是場噩夢啊……” “哼蹄衷!你這毒婦竟也來了厘肮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤耍属,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后厚骗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡夫嗓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年啤月,在試婚紗的時候發(fā)現(xiàn)自己被綠了劳跃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡郑诺,死狀恐怖杉武,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情飞涂,我是刑警寧澤祈搜,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布腿准,位于F島的核電站,受9級特大地震影響蘸秘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜醋虏,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毛秘。 院中可真熱鬧粘舟,春花似錦、人聲如沸柑肴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秽荞。三九已至,卻和暖如春抚官,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钦听。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工朴上, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痪宰。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓衣撬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淮韭。 傳聞我的和親對象是個殘疾皇子贴届,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一毫蚓、直接插入排序 直接插入排序(Insertion Sort)的基本思想是:每次將一個待排序的元素記錄,按其關(guān)鍵字...
    kevin16929閱讀 546評論 0 0
  • 概述 排序有內(nèi)部排序和外部排序畔乙,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進行排序翩概,而外部排序是因排序的數(shù)據(jù)很大返咱,一次不能容納全部...
    蟻前閱讀 5,164評論 0 52
  • 概述:排序有內(nèi)部排序和外部排序咖摹,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進行排序难述,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,727評論 0 15
  • 情人玫瑰和愛情 在我心中卻是骯臟煽情與交易 我不再相信神圣 因為失去了神圣的你
    雨野閱讀 244評論 4 6
  • 一天,一家三口出門裹纳。我習慣性地對兒子說:“寶貝,你的這件羽絨太薄了剃氧,適合在家穿阻星,外面冷,去換一件厚的吧妥箕。”兒子嘟囔...
    簡遐思閱讀 428評論 0 3