快速排序?qū)崿F(xiàn)及pivot的選取

coursera上斯坦福的算法專項(xiàng)在講到快速排序時(shí),稱其為最優(yōu)雅的算法之一酵颁。快速排序確實(shí)是一種比較有效的排序算法月帝,很多類庫中也都采用了這種排序算法躏惋,其最壞時(shí)間復(fù)雜度為O(n^2),平均時(shí)間復(fù)雜度為O(nlogn)嫁赏,且其不需要額外的存儲(chǔ)空間其掂。

基本步驟

快速排序主要使用了分治的思想,通過選取一個(gè)pivot潦蝇,將一個(gè)數(shù)組劃分為兩個(gè)子數(shù)組款熬。其步驟為:
1.從數(shù)組中選擇一個(gè)元素作為pivot
2.重新排列數(shù)組深寥,小于pivot的在pivot的左邊,大于pivot的在其右邊贤牛。
3.遞歸地對(duì)劃分后的左右兩部分重復(fù)上述步驟惋鹅。

簡(jiǎn)單的偽代碼如下:

<img src="http://ou5lyiz64.bkt.clouddn.com/img/qsort%E9%80%89%E5%8C%BA_001.png" style="zoom: 50%" />

其中最主要的就是partition劃分過程了。

劃分過程

partition過程需要首先選擇一個(gè)pivot殉簸,然后將小于pivot的元素放到左半部分闰集,大于pivot的放到右半部分,并且最終pivot的位置及為其在排序好的數(shù)組中的最終位置般卑。

這里使用第一個(gè)元素作為pivot武鲁,若選擇其他元素作為pivot,則將其交換到第一個(gè)元素蝠检,這樣可以保證代碼的一致性及容易實(shí)現(xiàn)沐鼠。示意圖如下:

<img src="http://ou5lyiz64.bkt.clouddn.com/img/qsort%E9%80%89%E5%8C%BA_002.png" style="zoom:50%" />

這里使用i和j,i和j最初為p+1的位置叹谁,在遍歷的過程中i始終指向>p的第一個(gè)元素饲梭,j始終指向當(dāng)前待遍歷的元素,若a[j] < p焰檩,則將其與a[i]進(jìn)行交換憔涉。相關(guān)過程如下:

<img src="http://ou5lyiz64.bkt.clouddn.com/img/qsort%E9%80%89%E5%8C%BA_003.png" style="zoom:50%" />

基本實(shí)現(xiàn)如下:

    /**
    * a[l+1],...,a[i-1] < p
    * a[i],...,a[j-1] > p
    */
    private static int partition(int[] a, int l, int r) {
        int p = a[l];

        int i = l + 1;
        for (int j=l+1; j<=r; j++) {
            if (a[j] < p) {
                swap(a, j, i);
                i++;
            }
        }
        swap(a, l, i-1);
        return i-1;
    }

基本實(shí)現(xiàn)

public class QuickSort {
    public static void qSort(int[] a) {
        if (a == null || a.length <= 1) {
            return;
        }
    
        qSort(a, 0, a.length-1);
    }

    private static void qSort(int[] a, int l, int r)    {
        if (l >= r) {
            return;
        }

        int pos = partition(a, l, r);

        qSort(a, l, pos - 1);
        qSort(a, pos + 1, r);
    }

    /**
    * a[l+1],...,a[i-1] < p
    * a[i],...,a[j-1] > p
    */
    private static int partition(int[] a, int l, int r) {
        int p = a[l];
        int i = l + 1;
        for (int j=l+1; j<=r; j++) {
            if (a[j] < p) {
                swap(a, j, i);
                i++;
            }
        }
        swap(a, l, i-1);
        return i-1;
    }

    //返回pivot下標(biāo) 選擇第一個(gè)元素
    private static int choosePivotFirst(int[] a, int l, int r) {
        return l;
    }
    
   private static void swap(int[] a, int x, int y) {
        int temp = a[x];
        a[x] = a[y];
        a[y] = temp;
    }

pivot的選取

根據(jù)斯坦福算法專項(xiàng)課,然我們實(shí)現(xiàn)三種不同的pivot選取方式析苫,并計(jì)算相應(yīng)比較次數(shù)兜叨,分別為choose first, choose last, median of three, 還可以進(jìn)行隨機(jī)選取,這也是快速排序?yàn)槭裁词且环N隨機(jī)化算法藤违。

pivot的選取決定了快速排序的運(yùn)行時(shí)間浪腐,下面對(duì)幾種特殊情況進(jìn)行分析:

1.最壞情況

假設(shè)我們始終選取第一個(gè)元素作為pivot, 并且輸入數(shù)組是有序的,那么每次劃分后面所有元素都大于pivot, 每次只能將問題規(guī)模減少1顿乒,所以運(yùn)行時(shí)間為n+n-1+n-2+...+1 = O(n^2).

2.最好情況

最好情況為每次選取的pivot都能將數(shù)組平均地劃分為兩部分议街,由于劃分的過程為O(n),所以總的運(yùn)行時(shí)間為T(n) = 2T(n/2) + O(n)根據(jù)主方法璧榄,時(shí)間復(fù)雜度為O(nlogn)特漩。

3.隨機(jī)選取

每次運(yùn)行過程中,隨機(jī)選取pivot, 通常能得到比較好的結(jié)果骨杂。

選取方式及實(shí)現(xiàn)

斯坦福算法專項(xiàng)課上讓我們實(shí)現(xiàn)三種不同的選取方式涂身,選取第一個(gè),最后一個(gè)搓蚪,以及三數(shù)取中蛤售。

1.choose first

該種方式最為簡(jiǎn)單,只需返回子數(shù)組的第一個(gè)元素下標(biāo)即可,下面為其實(shí)現(xiàn):

//返回pivot下標(biāo) 選擇第一個(gè)元素
private static int choosePivotFirst(int[] a, int l, int r) {
    return l;
}

2.choose last

選擇最后一個(gè)元素悴能,實(shí)現(xiàn)如下:

//選擇最后一個(gè)元素作為pivot
private static int choosePivotLast(int[] a, int l, int r) {
        return r;
}

3.median-of-three

選取第一個(gè)揣钦、最后一個(gè)以及中間的元素的中位數(shù),如4 5 6 7, 第一個(gè)4, 最后一個(gè)7, 中間的為5, 這三個(gè)數(shù)的中位數(shù)為5, 所以選擇5作為pivot漠酿,8 2 5 4 7, 三個(gè)元素分別為8 5 7, 中位數(shù)為7, 所以選擇最后一個(gè)元素7作為pivot冯凹,其實(shí)現(xiàn)如下:

//median-of-three pivot rule
private static int choosePivotMedianOfThree(int[] a, int l, int r) {    
    int mid = 0;
    if ((r-l+1) % 2 == 0) {
        mid = l + (r-l+1)/2 - 1;
    } else {
        mid = l + (r-l+1)/2;
    }

    //只需要找出中位數(shù)即可,不需要交換
    //有的版本也可以進(jìn)行交換
    if (((a[l]-a[mid]) * (a[l]-a[r])) <= 0) {
        return l;
    } else if (((a[mid]-a[l]) * (a[mid]-a[r])) <= 0)    {
        return mid;
    } else {
        return r;
    }
}

最后的劃分過程如下:

private static int partition(int[] a, int l, int r) {
    //pivot選擇方式
    //int pi = choosePivotFirst(a, l, r);
    //int pi = choosePivotLast(a, l, r);
    int pi = choosePivotMedianOfThree(a, l, r);

    //始終將第一個(gè)元素作為pivot, 若不是, 則與之交換
    if (pi != l) {
        swap(a, pi, l);
    }
    int p = a[l];

    int i = l + 1;
    for (int j=l+1; j<=r; j++) {
        if (a[j] < p) {
            swap(a, j, i);
            i++;
        }
    }
    swap(a, l, i-1);
    return i-1;
}

注意最后的劃分過程相比于之前增加的pivot的選取方式炒嘲,而不是單純地將第一個(gè)元素作為pivot, 可以看到宇姚,若第一個(gè)元素不是pivot, 需要將pivot與第一個(gè)元素進(jìn)行交換,這樣保證代碼的統(tǒng)一性夫凸。

總結(jié)與感想

1.學(xué)會(huì)體會(huì)這些算法背后的思想浑劳,為什么要這樣設(shè)計(jì)

2.對(duì)于比較復(fù)雜的算法,學(xué)會(huì)使用特殊情況進(jìn)行分析

參考資料:

(1) coursera斯坦福算法專項(xiàng)課part1

(2) 維基百科快速排序

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夭拌,一起剝皮案震驚了整個(gè)濱河市呀洲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌啼止,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兵罢,死亡現(xiàn)場(chǎng)離奇詭異献烦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)卖词,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門巩那,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人此蜈,你說我怎么就攤上這事即横。” “怎么了裆赵?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵东囚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我战授,道長(zhǎng)页藻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任植兰,我火速辦了婚禮份帐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘楣导。我一直安慰自己废境,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著噩凹,像睡著了一般巴元。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栓始,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天务冕,我揣著相機(jī)與錄音,去河邊找鬼幻赚。 笑死禀忆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的落恼。 我是一名探鬼主播箩退,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼佳谦!你這毒婦竟也來了戴涝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤钻蔑,失蹤者是張志新(化名)和其女友劉穎啥刻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咪笑,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡可帽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窗怒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片映跟。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扬虚,靈堂內(nèi)的尸體忽然破棺而出努隙,到底是詐尸還是另有隱情,我是刑警寧澤辜昵,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布荸镊,位于F島的核電站,受9級(jí)特大地震影響路鹰,放射性物質(zhì)發(fā)生泄漏贷洲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一晋柱、第九天 我趴在偏房一處隱蔽的房頂上張望优构。 院中可真熱鬧,春花似錦雁竞、人聲如沸钦椭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彪腔。三九已至侥锦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間德挣,已是汗流浹背恭垦。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留格嗅,地道東北人番挺。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像屯掖,于是被迫代替她去往敵國和親玄柏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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